nbt/
blob.rs

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