Skip to main content

nbtx/
lib.rs

1//! Implements NBT serialisation and deserialization for three different integer encodings.
2
3#![warn(clippy::pedantic)]
4#![allow(clippy::cast_possible_wrap)]
5#![allow(clippy::cast_possible_truncation)]
6#![allow(clippy::cast_lossless)]
7#![allow(clippy::enum_glob_use)]
8#![allow(clippy::must_use_candidate)]
9#![allow(clippy::missing_errors_doc)]
10// Ensures that docs.rs builds all features and displays which feature flags to use for the types.
11#![cfg_attr(docsrs, feature(doc_cfg))]
12
13use crate::error::TypeOutOfRange;
14pub use crate::nbt::de::{Deserializer, from_be_bytes, from_bytes, from_le_bytes, from_net_bytes};
15pub use crate::nbt::ser::{
16    Serializer, to_be_bytes, to_be_bytes_in, to_bytes, to_bytes_in, to_le_bytes, to_le_bytes_in,
17    to_net_bytes, to_net_bytes_in,
18};
19pub use crate::value::Value;
20pub use byteorder::{BigEndian, LittleEndian};
21
22use std::fmt::{self, Debug, Display};
23
24pub use error::{Error, Result};
25
26#[cfg(test)]
27mod test;
28
29mod error;
30mod nbt;
31
32#[cfg(feature = "snbt")]
33pub mod snbt;
34#[cfg(feature = "snbt")]
35pub use snbt::{from_string, to_string};
36
37mod value;
38
39mod private {
40    use byteorder::{BigEndian, LittleEndian};
41
42    use crate::{EndiannessImpl, NetworkLittleEndian, Variant};
43
44    /// Prevents [`VariantImpl`](super::VariantImpl) from being implemented for
45    /// types outside of this crate.
46    pub trait Sealed {}
47
48    impl Sealed for BigEndian {}
49    impl EndiannessImpl for BigEndian {
50        const AS_ENUM: Variant = Variant::BigEndian;
51    }
52
53    impl Sealed for LittleEndian {}
54    impl EndiannessImpl for LittleEndian {
55        const AS_ENUM: Variant = Variant::LittleEndian;
56    }
57
58    impl Sealed for NetworkLittleEndian {}
59    impl EndiannessImpl for NetworkLittleEndian {
60        const AS_ENUM: Variant = Variant::NetworkEndian;
61    }
62}
63
64/// Implemented by all NBT variants.
65pub trait EndiannessImpl: private::Sealed {
66    /// Used to convert a variant to an enum.
67    /// This is used to match generic types in order to prevent
68    /// having to duplicate all deserialisation code three times.
69    const AS_ENUM: Variant;
70}
71
72/// NBT format variant.
73#[derive(Debug, Copy, Clone, PartialEq, Eq)]
74pub enum Variant {
75    /// Used by Bedrock for data saved to disk.
76    /// Every data type is written in little endian format.
77    LittleEndian,
78    /// Used by Java.
79    /// Every data types is written in big endian format.
80    BigEndian,
81    /// Used by Bedrock for NBT transferred over the network.
82    /// This format is the same as [`LittleEndian`], except that type lengths
83    /// (such as for strings or lists), are varints instead of shorts.
84    /// The integer and long types are also varints.
85    NetworkEndian,
86}
87
88/// Used by Bedrock for NBT transferred over the network.
89/// This format is the same as [`LittleEndian`], except that type lengths
90/// (such as for strings or lists), are varints instead of shorts.
91/// The integer and long types are also varints.
92pub enum NetworkLittleEndian {}
93
94/// NBT field type
95// Compiler complains about unused enum variants even though they're constructed using a transmute.
96#[allow(dead_code)]
97#[derive(Debug, Copy, Clone, PartialEq, Eq)]
98#[repr(u8)]
99pub enum FieldType {
100    /// Indicates the end of a compound tag.
101    End = 0,
102    /// A signed byte.
103    Byte = 1,
104    /// A signed short.
105    Short = 2,
106    /// A signed int.
107    Int = 3,
108    /// A signed long.
109    Long = 4,
110    /// A float.
111    Float = 5,
112    /// A double.
113    Double = 6,
114    /// An array of byte tags.
115    ByteArray = 7,
116    /// A UTF-8 string.
117    String = 8,
118    /// List of tags.
119    /// Every item in the list must be of the same type.
120    List = 9,
121    /// A key-value map.
122    Compound = 10,
123    /// An array of int tags.
124    IntArray = 11,
125    /// An array of long tags.
126    LongArray = 12,
127}
128
129impl Display for FieldType {
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        use FieldType::*;
132
133        let str = match self {
134            End => "end",
135            Byte => "byte",
136            Short => "short",
137            Int => "int",
138            Long => "long",
139            Float => "float",
140            Double => "double",
141            ByteArray => "byte array",
142            String => "string",
143            List => "list",
144            Compound => "compound",
145            IntArray => "int array",
146            LongArray => "long array",
147        };
148
149        f.write_str(str)
150    }
151}
152
153impl FieldType {
154    pub(crate) fn try_from(
155        v: u8,
156        #[cfg(feature = "error-context")] at: &mut Option<String>,
157        #[cfg(feature = "error-context")] at_index: Option<usize>,
158    ) -> Result<Self> {
159        const LAST_DISC: u8 = FieldType::LongArray as u8;
160        if v > LAST_DISC {
161            return Err(Error::TypeOutOfRange(TypeOutOfRange {
162                found: v,
163
164                #[cfg(feature = "error-context")]
165                at: at.take().unwrap_or_else(|| String::from("unknown")),
166                #[cfg(feature = "error-context")]
167                index: at_index,
168            }));
169        }
170
171        // SAFETY: Because `Self` is marked as `repr(u8)`, its layout is guaranteed to start
172        // with a `u8` discriminant as its first field. Additionally, the raw discriminant is verified
173        // to be in the enum's range.
174        Ok(unsafe { std::mem::transmute::<u8, FieldType>(v) })
175    }
176}
177
178// impl TryFrom<u8> for FieldType {
179//     type Error = Error;
180
181//     fn try_from(v: u8) -> Result<Self> {
182//         const LAST_DISC: u8 = FieldType::LongArray as u8;
183//         if v > LAST_DISC {
184//             return Err(Error::TypeOutOfRange { actual: v });
185//         }
186
187//         // SAFETY: Because `Self` is marked as `repr(u8)`, its layout is guaranteed to start
188//         // with a `u8` discriminant as its first field. Additionally, the raw discriminant is verified
189//         // to be in the enum's range.
190//         Ok(unsafe { std::mem::transmute::<u8, FieldType>(v) })
191//     }
192// }
193
194impl serde::de::Error for Error {
195    fn custom<T>(msg: T) -> Self
196    where
197        T: Display,
198    {
199        Error::Other(msg.to_string())
200    }
201}
202
203impl serde::ser::Error for Error {
204    fn custom<T>(msg: T) -> Self
205    where
206        T: Display,
207    {
208        Error::Other(msg.to_string())
209    }
210}