nbt/
blob.rs

1use crate::Map;
2use std::fmt;
3use std::io;
4use std::ops::Index;
5
6use byteorder::WriteBytesExt;
7use flate2::read::{GzDecoder, ZlibDecoder};
8use flate2::write::{GzEncoder, ZlibEncoder};
9use flate2::Compression;
10
11use error::{Error, Result};
12use raw;
13use value::Value;
14
15/// A generic, complete object in Named Binary Tag format.
16///
17/// This is essentially a map of names to `Value`s, with an optional top-level
18/// name of its own. It can be created in a similar way to a `HashMap`, or read
19/// from an `io::Read` source, and its binary representation can be written to
20/// an `io::Write` destination.
21///
22/// These read and write methods support both uncompressed and compressed
23/// (through Gzip or zlib compression) methods.
24///
25/// ```rust
26/// use nbt::{Blob, Value};
27///
28/// // Create a `Blob` from key/value pairs.
29/// let mut nbt = Blob::new();
30/// nbt.insert("name", "Herobrine").unwrap();
31/// nbt.insert("health", 100i8).unwrap();
32/// nbt.insert("food", 20.0f32).unwrap();
33///
34/// // Write a compressed binary representation to a byte array.
35/// let mut dst = Vec::new();
36/// nbt.to_zlib_writer(&mut dst).unwrap();
37/// ```
38#[derive(Clone, Debug, Default, PartialEq)]
39pub struct Blob {
40    title: String,
41    content: Map<String, Value>,
42}
43
44impl Blob {
45    /// Create a new NBT file format representation with an empty name.
46    pub fn new() -> Blob {
47        Blob {
48            title: "".to_string(),
49            content: Map::new(),
50        }
51    }
52
53    /// Create a new NBT file format representation with the given name.
54    pub fn named<S>(name: S) -> Blob
55    where
56        S: Into<String>,
57    {
58        Blob {
59            title: name.into(),
60            content: Map::new(),
61        }
62    }
63
64    /// Extracts an `Blob` object from an `io::Read` source.
65    pub fn from_reader<R>(src: &mut R) -> Result<Blob>
66    where
67        R: io::Read,
68    {
69        let (tag, title) = raw::emit_next_header(src)?;
70        // Although it would be possible to read NBT format files composed of
71        // arbitrary objects using the current API, by convention all files
72        // have a top-level Compound.
73        if tag != 0x0a {
74            return Err(Error::NoRootCompound);
75        }
76        let content = Value::from_reader(tag, src)?;
77        match content {
78            Value::Compound(map) => Ok(Blob {
79                title,
80                content: map,
81            }),
82            _ => Err(Error::NoRootCompound),
83        }
84    }
85
86    /// Extracts an `Blob` object from an `io::Read` source that is
87    /// compressed using the Gzip format.
88    pub fn from_gzip_reader<R>(src: &mut R) -> Result<Blob>
89    where
90        R: io::Read,
91    {
92        // Reads the gzip header, and fails if it is incorrect.
93        let mut data = GzDecoder::new(src);
94        Blob::from_reader(&mut data)
95    }
96
97    /// Extracts an `Blob` object from an `io::Read` source that is
98    /// compressed using the zlib format.
99    pub fn from_zlib_reader<R>(src: &mut R) -> Result<Blob>
100    where
101        R: io::Read,
102    {
103        Blob::from_reader(&mut ZlibDecoder::new(src))
104    }
105
106    /// Writes the binary representation of this `Blob` to an `io::Write`
107    /// destination.
108    pub fn to_writer<W>(&self, mut dst: &mut W) -> Result<()>
109    where
110        W: io::Write,
111    {
112        dst.write_u8(0x0a)?;
113        raw::write_bare_string(&mut dst, &self.title)?;
114        for (name, ref nbt) in self.content.iter() {
115            dst.write_u8(nbt.id())?;
116            raw::write_bare_string(&mut dst, name)?;
117            nbt.to_writer(&mut dst)?;
118        }
119        raw::close_nbt(&mut dst)
120    }
121
122    /// Writes the binary representation of this `Blob`, compressed using
123    /// the Gzip format, to an `io::Write` destination.
124    pub fn to_gzip_writer<W>(&self, dst: &mut W) -> Result<()>
125    where
126        W: io::Write,
127    {
128        self.to_writer(&mut GzEncoder::new(dst, Compression::default()))
129    }
130
131    /// Writes the binary representation of this `Blob`, compressed using
132    /// the Zlib format, to an `io::Write` dst.
133    pub fn to_zlib_writer<W>(&self, dst: &mut W) -> Result<()>
134    where
135        W: io::Write,
136    {
137        self.to_writer(&mut ZlibEncoder::new(dst, Compression::default()))
138    }
139
140    /// Insert an `Value` with a given name into this `Blob` object. This
141    /// method is just a thin wrapper around the underlying map method of
142    /// the same name.
143    ///
144    /// This method will also return an error if a `Value::List` with
145    /// heterogeneous elements is passed in, because this is illegal in the NBT
146    /// file format.
147    pub fn insert<S, V>(&mut self, name: S, value: V) -> Result<()>
148    where
149        S: Into<String>,
150        V: Into<Value>,
151    {
152        // The follow prevents `List`s with heterogeneous tags from being
153        // inserted into the file.
154        let nvalue = value.into();
155        if let Value::List(ref vals) = nvalue {
156            if !vals.is_empty() {
157                let first_id = vals[0].id();
158                for nbt in vals {
159                    if nbt.id() != first_id {
160                        return Err(Error::HeterogeneousList);
161                    }
162                }
163            }
164        }
165        self.content.insert(name.into(), nvalue);
166        Ok(())
167    }
168
169    /// Tries to get a named `Value` in the blob.
170    pub fn get<S>(&self, name: S) -> Option<&Value>
171    where
172        S: Into<&'static str>,
173    {
174        self.content.get(name.into())
175    }
176
177    /// The number of bytes this blob will serialize to, before compression
178    pub fn len_bytes(&self) -> usize {
179        /* compound tag + name length + TAG_End = 4 */
180        4 + self.title.len()
181            + self
182                .content
183                .iter()
184                .map(Value::size_of_compound_entry)
185                .sum::<usize>()
186    }
187}
188
189impl<'a> Index<&'a str> for Blob {
190    type Output = Value;
191
192    fn index<'b>(&'b self, s: &'a str) -> &'b Value {
193        self.content.get(s).unwrap()
194    }
195}
196
197impl fmt::Display for Blob {
198    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
199        write!(
200            f,
201            "TAG_Compound(\"{}\"): {} entry(ies)\n{{\n",
202            self.title,
203            self.content.len()
204        )?;
205        for (name, tag) in self.content.iter() {
206            write!(f, "  {}(\"{}\"): ", tag.tag_name(), name)?;
207            tag.print(f, 2)?;
208            writeln!(f)?;
209        }
210        write!(f, "}}")
211    }
212}
213
214#[cfg(feature = "serde")]
215use serde::{self, ser::SerializeMap};
216
217#[cfg(feature = "serde")]
218impl serde::Serialize for Blob {
219    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
220    where
221        S: serde::ser::Serializer,
222    {
223        // No support for named Blobs.
224        let mut state = serializer.serialize_map(Some(self.content.len()))?;
225        for (k, v) in &self.content {
226            state.serialize_entry(&k, &v)?;
227        }
228        state.end()
229    }
230}
231
232#[cfg(feature = "serde")]
233impl<'de> serde::Deserialize<'de> for Blob {
234    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
235    where
236        D: serde::de::Deserializer<'de>,
237    {
238        // No support for named Blobs.
239        let map: Map<String, Value> = serde::de::Deserialize::deserialize(deserializer)?;
240        Ok(Blob {
241            title: "".to_string(),
242            content: map,
243        })
244    }
245}