mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 14:31:30 +11:00
Move valence_nbt to main Valence repo. (#97)
This also adds another check in CI
This commit is contained in:
parent
ef64296159
commit
9c62bc1b90
|
@ -32,3 +32,5 @@ jobs:
|
|||
run: cargo +stable clippy --no-deps --all-features --all-targets -- -D warnings
|
||||
- name: Run tests
|
||||
run: cargo +stable test --all-features --all-targets
|
||||
- name: Run valence_nbt tests without preserve_order
|
||||
run: cargo +stable test -p valence_nbt
|
|
@ -69,7 +69,7 @@ rayon = "1.5.3"
|
|||
num = "0.4.0"
|
||||
|
||||
[workspace]
|
||||
members = ["packet_inspector"]
|
||||
members = ["valence_nbt", "packet_inspector"]
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
|
21
valence_nbt/Cargo.toml
Normal file
21
valence_nbt/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "valence_nbt"
|
||||
description = "A library for Minecraft's Named Binary Tag (NBT) format."
|
||||
documentation = "https://docs.rs/valence_nbt/"
|
||||
repository = "https://github.com/valence-rs/valence_nbt"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
keywords = ["nbt", "minecraft", "serialization"]
|
||||
version = "0.1.2"
|
||||
authors = ["Ryan Johnson <ryanj00a@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4.3"
|
||||
cesu8 = "1.1.0"
|
||||
indexmap = { version = "1.9.1", optional = true }
|
||||
zerocopy = "0.6.1"
|
||||
|
||||
[features]
|
||||
# When enabled, the order of fields in compounds are preserved.
|
||||
preserve_order = ["dep:indexmap"]
|
8
valence_nbt/README.md
Normal file
8
valence_nbt/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# valence_nbt
|
||||
|
||||
A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT) format.
|
||||
|
||||
See the [documentation] for more information.
|
||||
|
||||
[Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format
|
||||
[documentation]: https://docs.rs/valence_nbt/
|
413
valence_nbt/src/compound.rs
Normal file
413
valence_nbt/src/compound.rs
Normal file
|
@ -0,0 +1,413 @@
|
|||
use std::borrow::Borrow;
|
||||
use std::hash::Hash;
|
||||
use std::iter::FusedIterator;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use crate::Value;
|
||||
|
||||
/// A map type with [`String`] keys and [`Value`] values.
|
||||
#[derive(Clone, PartialEq, Default, Debug)]
|
||||
pub struct Compound {
|
||||
map: Map,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
type Map = std::collections::HashMap<String, Value>;
|
||||
|
||||
#[cfg(feature = "preserve_order")]
|
||||
type Map = indexmap::IndexMap<String, Value>;
|
||||
|
||||
impl Compound {
|
||||
pub fn new() -> Self {
|
||||
Self { map: Map::new() }
|
||||
}
|
||||
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self {
|
||||
map: Map::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.map.clear();
|
||||
}
|
||||
|
||||
pub fn get<Q>(&self, k: &Q) -> Option<&Value>
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
self.map.get(k)
|
||||
}
|
||||
|
||||
pub fn contains_key<Q>(&self, k: &Q) -> bool
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
self.map.contains_key(k)
|
||||
}
|
||||
|
||||
pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut Value>
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
self.map.get_mut(k)
|
||||
}
|
||||
|
||||
pub fn get_key_value<Q>(&self, k: &Q) -> Option<(&String, &Value)>
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
self.map.get_key_value(k)
|
||||
}
|
||||
|
||||
pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<Value>
|
||||
where
|
||||
K: Into<String>,
|
||||
V: Into<Value>,
|
||||
{
|
||||
self.map.insert(k.into(), v.into())
|
||||
}
|
||||
|
||||
pub fn remove<Q>(&mut self, k: &Q) -> Option<Value>
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
self.map.remove(k)
|
||||
}
|
||||
|
||||
pub fn remove_entry<Q>(&mut self, k: &Q) -> Option<(String, Value)>
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
self.map.remove_entry(k)
|
||||
}
|
||||
|
||||
// TODO: append function for potential BTreeMap usage?
|
||||
|
||||
pub fn entry<K>(&mut self, k: K) -> Entry
|
||||
where
|
||||
K: Into<String>,
|
||||
{
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
use std::collections::hash_map::Entry as EntryImpl;
|
||||
|
||||
#[cfg(feature = "preserve_order")]
|
||||
use indexmap::map::Entry as EntryImpl;
|
||||
|
||||
match self.map.entry(k.into()) {
|
||||
EntryImpl::Vacant(ve) => Entry::Vacant(VacantEntry { ve }),
|
||||
EntryImpl::Occupied(oe) => Entry::Occupied(OccupiedEntry { oe }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter {
|
||||
Iter {
|
||||
iter: self.map.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> IterMut {
|
||||
IterMut {
|
||||
iter: self.map.iter_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> Keys {
|
||||
Keys {
|
||||
iter: self.map.keys(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values(&self) -> Values {
|
||||
Values {
|
||||
iter: self.map.values(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> ValuesMut {
|
||||
ValuesMut {
|
||||
iter: self.map.values_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retain<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnMut(&String, &mut Value) -> bool,
|
||||
{
|
||||
self.map.retain(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(String, Value)> for Compound {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = (String, Value)>,
|
||||
{
|
||||
self.map.extend(iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Value)> for Compound {
|
||||
fn from_iter<T>(iter: T) -> Self
|
||||
where
|
||||
T: IntoIterator<Item = (String, Value)>,
|
||||
{
|
||||
Self {
|
||||
map: Map::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Entry<'a> {
|
||||
Vacant(VacantEntry<'a>),
|
||||
Occupied(OccupiedEntry<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Entry<'a> {
|
||||
pub fn key(&self) -> &String {
|
||||
match self {
|
||||
Entry::Vacant(ve) => ve.key(),
|
||||
Entry::Occupied(oe) => oe.key(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or_insert(self, default: impl Into<Value>) -> &'a mut Value {
|
||||
match self {
|
||||
Entry::Vacant(ve) => ve.insert(default),
|
||||
Entry::Occupied(oe) => oe.into_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or_insert_with<F, V>(self, default: F) -> &'a mut Value
|
||||
where
|
||||
F: FnOnce() -> V,
|
||||
V: Into<Value>,
|
||||
{
|
||||
match self {
|
||||
Entry::Vacant(ve) => ve.insert(default()),
|
||||
Entry::Occupied(oe) => oe.into_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and_modify<F>(self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut Value),
|
||||
{
|
||||
match self {
|
||||
Entry::Vacant(ve) => Entry::Vacant(ve),
|
||||
Entry::Occupied(mut oe) => {
|
||||
f(oe.get_mut());
|
||||
Entry::Occupied(oe)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VacantEntry<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
ve: std::collections::hash_map::VacantEntry<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
ve: indexmap::map::VacantEntry<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl<'a> VacantEntry<'a> {
|
||||
pub fn key(&self) -> &String {
|
||||
self.ve.key()
|
||||
}
|
||||
|
||||
pub fn insert(self, v: impl Into<Value>) -> &'a mut Value {
|
||||
self.ve.insert(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OccupiedEntry<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
oe: std::collections::hash_map::OccupiedEntry<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
oe: indexmap::map::OccupiedEntry<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl<'a> OccupiedEntry<'a> {
|
||||
pub fn key(&self) -> &String {
|
||||
self.oe.key()
|
||||
}
|
||||
|
||||
pub fn get(&self) -> &Value {
|
||||
self.oe.get()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self) -> &mut Value {
|
||||
self.oe.get_mut()
|
||||
}
|
||||
|
||||
pub fn into_mut(self) -> &'a mut Value {
|
||||
self.oe.into_mut()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, v: impl Into<Value>) -> Value {
|
||||
self.oe.insert(v.into())
|
||||
}
|
||||
|
||||
pub fn remove(self) -> Value {
|
||||
self.oe.remove()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q> Index<&'_ Q> for Compound
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
type Output = Value;
|
||||
|
||||
fn index(&self, index: &Q) -> &Self::Output {
|
||||
self.map.index(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q> IndexMut<&'_ Q> for Compound
|
||||
where
|
||||
String: Borrow<Q>,
|
||||
Q: ?Sized + Eq + Ord + Hash,
|
||||
{
|
||||
fn index_mut(&mut self, index: &Q) -> &mut Self::Output {
|
||||
self.map.get_mut(index).expect("no entry found for key")
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_iterator_traits {
|
||||
(($name:ident $($generics:tt)*) => $item:ty) => {
|
||||
impl $($generics)* Iterator for $name $($generics)* {
|
||||
type Item = $item;
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next()
|
||||
}
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.iter.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "preserve_order")]
|
||||
impl $($generics)* DoubleEndedIterator for $name $($generics)* {
|
||||
#[inline]
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
impl $($generics)* ExactSizeIterator for $name $($generics)* {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.iter.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl $($generics)* FusedIterator for $name $($generics)* {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Compound {
|
||||
type Item = (&'a String, &'a Value);
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
Iter {
|
||||
iter: self.map.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Iter<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
iter: std::collections::hash_map::Iter<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
iter: indexmap::map::Iter<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl_iterator_traits!((Iter<'a>) => (&'a String, &'a Value));
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Compound {
|
||||
type Item = (&'a String, &'a mut Value);
|
||||
type IntoIter = IterMut<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IterMut {
|
||||
iter: self.map.iter_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IterMut<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
iter: std::collections::hash_map::IterMut<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
iter: indexmap::map::IterMut<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl_iterator_traits!((IterMut<'a>) => (&'a String, &'a mut Value));
|
||||
|
||||
impl IntoIterator for Compound {
|
||||
type Item = (String, Value);
|
||||
type IntoIter = IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter {
|
||||
iter: self.map.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IntoIter {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
iter: std::collections::hash_map::IntoIter<String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
iter: indexmap::map::IntoIter<String, Value>,
|
||||
}
|
||||
|
||||
impl_iterator_traits!((IntoIter) => (String, Value));
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Keys<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
iter: std::collections::hash_map::Keys<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
iter: indexmap::map::Keys<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl_iterator_traits!((Keys<'a>) => &'a String);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Values<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
iter: std::collections::hash_map::Values<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
iter: indexmap::map::Values<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl_iterator_traits!((Values<'a>) => &'a Value);
|
||||
|
||||
pub struct ValuesMut<'a> {
|
||||
#[cfg(not(feature = "preserve_order"))]
|
||||
iter: std::collections::hash_map::ValuesMut<'a, String, Value>,
|
||||
#[cfg(feature = "preserve_order")]
|
||||
iter: indexmap::map::ValuesMut<'a, String, Value>,
|
||||
}
|
||||
|
||||
impl_iterator_traits!((ValuesMut<'a>) => &'a mut Value);
|
59
valence_nbt/src/error.rs
Normal file
59
valence_nbt/src/error.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use std::error::Error as StdError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
|
||||
/// Errors that can occur when encoding or decoding.
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// Box this to keep the size of `Result<T, Error>` small.
|
||||
cause: Box<Cause>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Cause {
|
||||
Io(io::Error),
|
||||
Owned(Box<str>),
|
||||
Static(&'static str),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn new_owned(msg: impl Into<Box<str>>) -> Self {
|
||||
Self {
|
||||
cause: Box::new(Cause::Owned(msg.into())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_static(msg: &'static str) -> Self {
|
||||
Self {
|
||||
cause: Box::new(Cause::Static(msg)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match &*self.cause {
|
||||
Cause::Io(e) => e.fmt(f),
|
||||
Cause::Owned(msg) => write!(f, "{msg}"),
|
||||
Cause::Static(msg) => write!(f, "{msg}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for Error {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match &*self.cause {
|
||||
Cause::Io(e) => Some(e),
|
||||
Cause::Owned(_) => None,
|
||||
Cause::Static(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self {
|
||||
cause: Box::new(Cause::Io(e)),
|
||||
}
|
||||
}
|
||||
}
|
299
valence_nbt/src/from_binary_slice.rs
Normal file
299
valence_nbt/src/from_binary_slice.rs
Normal file
|
@ -0,0 +1,299 @@
|
|||
use std::mem;
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use cesu8::Cesu8DecodingError;
|
||||
|
||||
use crate::tag::Tag;
|
||||
use crate::{Compound, Error, List, Result, Value, MAX_DEPTH};
|
||||
|
||||
/// Decodes uncompressed NBT binary data from the provided slice.
|
||||
///
|
||||
/// The string returned is the name of the root compound.
|
||||
pub fn from_binary_slice(slice: &mut &[u8]) -> Result<(Compound, String)> {
|
||||
let mut state = DecodeState { slice, depth: 0 };
|
||||
|
||||
let root_tag = state.read_tag()?;
|
||||
if root_tag != Tag::Compound {
|
||||
return Err(Error::new_owned(format!(
|
||||
"expected root tag for compound (got {root_tag})",
|
||||
)));
|
||||
}
|
||||
|
||||
let root_name = state.read_string()?;
|
||||
let root = state.read_compound()?;
|
||||
|
||||
debug_assert_eq!(state.depth, 0);
|
||||
|
||||
Ok((root, root_name))
|
||||
}
|
||||
|
||||
struct DecodeState<'a, 'b> {
|
||||
slice: &'a mut &'b [u8],
|
||||
/// Current recursion depth.
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl DecodeState<'_, '_> {
|
||||
#[inline]
|
||||
fn check_depth<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
|
||||
if self.depth >= MAX_DEPTH {
|
||||
return Err(Error::new_static("reached maximum recursion depth"));
|
||||
}
|
||||
|
||||
self.depth += 1;
|
||||
let res = f(self);
|
||||
self.depth -= 1;
|
||||
res
|
||||
}
|
||||
|
||||
fn read_tag(&mut self) -> Result<Tag> {
|
||||
match self.slice.read_u8()? {
|
||||
0 => Ok(Tag::End),
|
||||
1 => Ok(Tag::Byte),
|
||||
2 => Ok(Tag::Short),
|
||||
3 => Ok(Tag::Int),
|
||||
4 => Ok(Tag::Long),
|
||||
5 => Ok(Tag::Float),
|
||||
6 => Ok(Tag::Double),
|
||||
7 => Ok(Tag::ByteArray),
|
||||
8 => Ok(Tag::String),
|
||||
9 => Ok(Tag::List),
|
||||
10 => Ok(Tag::Compound),
|
||||
11 => Ok(Tag::IntArray),
|
||||
12 => Ok(Tag::LongArray),
|
||||
byte => Err(Error::new_owned(format!("invalid tag byte of {byte:#x}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_value(&mut self, tag: Tag) -> Result<Value> {
|
||||
match tag {
|
||||
Tag::End => unreachable!("illegal TAG_End argument"),
|
||||
Tag::Byte => Ok(self.read_byte()?.into()),
|
||||
Tag::Short => Ok(self.read_short()?.into()),
|
||||
Tag::Int => Ok(self.read_int()?.into()),
|
||||
Tag::Long => Ok(self.read_long()?.into()),
|
||||
Tag::Float => Ok(self.read_float()?.into()),
|
||||
Tag::Double => Ok(self.read_double()?.into()),
|
||||
Tag::ByteArray => Ok(self.read_byte_array()?.into()),
|
||||
Tag::String => Ok(self.read_string()?.into()),
|
||||
Tag::List => self.check_depth(|st| Ok(st.read_any_list()?.into())),
|
||||
Tag::Compound => self.check_depth(|st| Ok(st.read_compound()?.into())),
|
||||
Tag::IntArray => Ok(self.read_int_array()?.into()),
|
||||
Tag::LongArray => Ok(self.read_long_array()?.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_byte(&mut self) -> Result<i8> {
|
||||
Ok(self.slice.read_i8()?)
|
||||
}
|
||||
|
||||
fn read_short(&mut self) -> Result<i16> {
|
||||
Ok(self.slice.read_i16::<BigEndian>()?)
|
||||
}
|
||||
|
||||
fn read_int(&mut self) -> Result<i32> {
|
||||
Ok(self.slice.read_i32::<BigEndian>()?)
|
||||
}
|
||||
|
||||
fn read_long(&mut self) -> Result<i64> {
|
||||
Ok(self.slice.read_i64::<BigEndian>()?)
|
||||
}
|
||||
|
||||
fn read_float(&mut self) -> Result<f32> {
|
||||
Ok(self.slice.read_f32::<BigEndian>()?)
|
||||
}
|
||||
|
||||
fn read_double(&mut self) -> Result<f64> {
|
||||
Ok(self.slice.read_f64::<BigEndian>()?)
|
||||
}
|
||||
|
||||
fn read_byte_array(&mut self) -> Result<Vec<i8>> {
|
||||
let len = self.slice.read_i32::<BigEndian>()?;
|
||||
|
||||
if len.is_negative() {
|
||||
return Err(Error::new_owned(format!(
|
||||
"negative byte array length of {len}"
|
||||
)));
|
||||
}
|
||||
|
||||
if len as usize > self.slice.len() {
|
||||
return Err(Error::new_owned(format!(
|
||||
"byte array length of {len} exceeds remainder of input"
|
||||
)));
|
||||
}
|
||||
|
||||
let (left, right) = self.slice.split_at(len as usize);
|
||||
|
||||
let array = left.iter().map(|b| *b as i8).collect();
|
||||
*self.slice = right;
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<String> {
|
||||
let len = self.slice.read_u16::<BigEndian>()?.into();
|
||||
|
||||
if len > self.slice.len() {
|
||||
return Err(Error::new_owned(format!(
|
||||
"string of length {len} exceeds remainder of input"
|
||||
)));
|
||||
}
|
||||
|
||||
let (left, right) = self.slice.split_at(len);
|
||||
|
||||
match cesu8::from_java_cesu8(left) {
|
||||
Ok(cow) => {
|
||||
*self.slice = right;
|
||||
Ok(cow.into())
|
||||
}
|
||||
Err(Cesu8DecodingError) => {
|
||||
Err(Error::new_static("could not convert CESU-8 data to UTF-8"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_any_list(&mut self) -> Result<List> {
|
||||
match self.read_tag()? {
|
||||
Tag::End => match self.read_int()? {
|
||||
0 => Ok(List::Byte(Vec::new())),
|
||||
len => Err(Error::new_owned(format!(
|
||||
"TAG_End list with nonzero length of {len}"
|
||||
))),
|
||||
},
|
||||
Tag::Byte => Ok(self
|
||||
.read_list::<_, _, 1>(Tag::Byte, |st| st.read_byte())?
|
||||
.into()),
|
||||
Tag::Short => Ok(self
|
||||
.read_list::<_, _, 2>(Tag::Short, |st| st.read_short())?
|
||||
.into()),
|
||||
Tag::Int => Ok(self
|
||||
.read_list::<_, _, 4>(Tag::Int, |st| st.read_int())?
|
||||
.into()),
|
||||
Tag::Long => Ok(self
|
||||
.read_list::<_, _, 8>(Tag::Long, |st| st.read_long())?
|
||||
.into()),
|
||||
Tag::Float => Ok(self
|
||||
.read_list::<_, _, 4>(Tag::Float, |st| st.read_float())?
|
||||
.into()),
|
||||
Tag::Double => Ok(self
|
||||
.read_list::<_, _, 8>(Tag::Double, |st| st.read_double())?
|
||||
.into()),
|
||||
Tag::ByteArray => Ok(self
|
||||
.read_list::<_, _, 4>(Tag::ByteArray, |st| st.read_byte_array())?
|
||||
.into()),
|
||||
Tag::String => Ok(self
|
||||
.read_list::<_, _, 2>(Tag::String, |st| st.read_string())?
|
||||
.into()),
|
||||
Tag::List => self.check_depth(|st| {
|
||||
Ok(st
|
||||
.read_list::<_, _, 5>(Tag::List, |st| st.read_any_list())?
|
||||
.into())
|
||||
}),
|
||||
Tag::Compound => self.check_depth(|st| {
|
||||
Ok(st
|
||||
.read_list::<_, _, 1>(Tag::Compound, |st| st.read_compound())?
|
||||
.into())
|
||||
}),
|
||||
Tag::IntArray => Ok(self
|
||||
.read_list::<_, _, 4>(Tag::IntArray, |st| st.read_int_array())?
|
||||
.into()),
|
||||
Tag::LongArray => Ok(self
|
||||
.read_list::<_, _, 4>(Tag::LongArray, |st| st.read_long_array())?
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes the element tag has already been read.
|
||||
///
|
||||
/// `MIN_ELEM_SIZE` is the minimum size of the list element when encoded.
|
||||
fn read_list<T, F, const MIN_ELEM_SIZE: usize>(
|
||||
&mut self,
|
||||
elem_type: Tag,
|
||||
mut read_elem: F,
|
||||
) -> Result<Vec<T>>
|
||||
where
|
||||
F: FnMut(&mut Self) -> Result<T>,
|
||||
{
|
||||
let len = self.read_int()?;
|
||||
|
||||
if len.is_negative() {
|
||||
return Err(Error::new_owned(format!(
|
||||
"negative {elem_type} list length of {len}",
|
||||
)));
|
||||
}
|
||||
|
||||
// Ensure we don't reserve more than the maximum amount of memory required given
|
||||
// the size of the remaining input.
|
||||
if len as u64 * MIN_ELEM_SIZE as u64 > self.slice.len() as u64 {
|
||||
return Err(Error::new_owned(format!(
|
||||
"{elem_type} list of length {len} exceeds remainder of input"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut list = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len {
|
||||
list.push(read_elem(self)?);
|
||||
}
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
fn read_compound(&mut self) -> Result<Compound> {
|
||||
let mut compound = Compound::new();
|
||||
|
||||
loop {
|
||||
let tag = self.read_tag()?;
|
||||
if tag == Tag::End {
|
||||
return Ok(compound);
|
||||
}
|
||||
|
||||
compound.insert(self.read_string()?, self.read_value(tag)?);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_int_array(&mut self) -> Result<Vec<i32>> {
|
||||
let len = self.read_int()?;
|
||||
|
||||
if len.is_negative() {
|
||||
return Err(Error::new_owned(format!(
|
||||
"negative int array length of {len}",
|
||||
)));
|
||||
}
|
||||
|
||||
if len as u64 * mem::size_of::<i32>() as u64 > self.slice.len() as u64 {
|
||||
return Err(Error::new_owned(format!(
|
||||
"int array of length {len} exceeds remainder of input"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut array = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len {
|
||||
array.push(self.read_int()?);
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
fn read_long_array(&mut self) -> Result<Vec<i64>> {
|
||||
let len = self.read_int()?;
|
||||
|
||||
if len.is_negative() {
|
||||
return Err(Error::new_owned(format!(
|
||||
"negative long array length of {len}",
|
||||
)));
|
||||
}
|
||||
|
||||
if len as u64 * mem::size_of::<i64>() as u64 > self.slice.len() as u64 {
|
||||
return Err(Error::new_owned(format!(
|
||||
"long array of length {len} exceeds remainder of input"
|
||||
)));
|
||||
}
|
||||
|
||||
let mut array = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len {
|
||||
array.push(self.read_long()?);
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
}
|
118
valence_nbt/src/lib.rs
Normal file
118
valence_nbt/src/lib.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
//! A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT)
|
||||
//! format.
|
||||
//!
|
||||
//! [Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Encode NBT data to its binary form. We are using the [`compound!`] macro to
|
||||
//! conveniently construct [`Compound`] values.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use valence_nbt::{compound, to_binary_writer, List};
|
||||
//!
|
||||
//! let c = compound! {
|
||||
//! "byte" => 5_i8,
|
||||
//! "string" => "hello",
|
||||
//! "list_of_float" => List::Float(vec![
|
||||
//! 3.1415,
|
||||
//! 2.7182,
|
||||
//! 1.4142
|
||||
//! ]),
|
||||
//! };
|
||||
//!
|
||||
//! let mut buf = Vec::new();
|
||||
//!
|
||||
//! to_binary_writer(&mut buf, &c, "").unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! Decode NBT data from its binary form.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use valence_nbt::{compound, from_binary_slice};
|
||||
//!
|
||||
//! let some_bytes = [10, 0, 0, 3, 0, 3, 105, 110, 116, 0, 0, 222, 173, 0];
|
||||
//!
|
||||
//! let expected_value = compound! {
|
||||
//! "int" => 0xdead
|
||||
//! };
|
||||
//!
|
||||
//! let (nbt, root_name) = from_binary_slice(&mut some_bytes.as_slice()).unwrap();
|
||||
//!
|
||||
//! assert_eq!(nbt, expected_value);
|
||||
//! assert_eq!(root_name, "");
|
||||
//! ```
|
||||
//!
|
||||
//! # Features
|
||||
//!
|
||||
//! - `preserve_order`: Causes the order of fields in [`Compound`]s to be
|
||||
//! preserved during insertion and deletion at a slight cost to performance.
|
||||
//! The iterators on `Compound` can then implement [`DoubleEndedIterator`].
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
pub use compound::Compound;
|
||||
pub use error::Error;
|
||||
pub use from_binary_slice::from_binary_slice;
|
||||
pub use to_binary_writer::to_binary_writer;
|
||||
pub use value::{List, Value};
|
||||
|
||||
pub mod compound;
|
||||
mod error;
|
||||
mod from_binary_slice;
|
||||
mod to_binary_writer;
|
||||
pub mod value;
|
||||
|
||||
mod tag;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Maximum recursion depth to prevent overflowing the call stack.
|
||||
const MAX_DEPTH: usize = 512;
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A convenience macro for constructing [`Compound`]s.
|
||||
///
|
||||
/// Key expressions must implement `Into<String>` while value expressions must
|
||||
/// implement `Into<Value>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use valence_nbt::{compound, List};
|
||||
///
|
||||
/// let c = compound! {
|
||||
/// "byte" => 123_i8,
|
||||
/// "list_of_int" => List::Int(vec![3, -7, 5]),
|
||||
/// "list_of_string" => List::String(vec![
|
||||
/// "foo".to_owned(),
|
||||
/// "bar".to_owned(),
|
||||
/// "baz".to_owned()
|
||||
/// ]),
|
||||
/// "string" => "aé日",
|
||||
/// "compound" => compound! {
|
||||
/// "foo" => 1,
|
||||
/// "bar" => 2,
|
||||
/// "baz" => 3,
|
||||
/// },
|
||||
/// "int_array" => vec![5, -9, i32::MIN, 0, i32::MAX],
|
||||
/// "byte_array" => vec![0_i8, 2, 3],
|
||||
/// "long_array" => vec![123_i64, 456, 789],
|
||||
/// };
|
||||
///
|
||||
/// println!("{c:?}");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! compound {
|
||||
($($key:expr => $value:expr),* $(,)?) => {
|
||||
<$crate::Compound as ::std::iter::FromIterator<(::std::string::String, $crate::Value)>>::from_iter([
|
||||
$(
|
||||
(
|
||||
::std::convert::Into::<::std::string::String>::into($key),
|
||||
::std::convert::Into::<$crate::Value>::into($value)
|
||||
),
|
||||
)*
|
||||
])
|
||||
}
|
||||
}
|
65
valence_nbt/src/tag.rs
Normal file
65
valence_nbt/src/tag.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use crate::Value;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Tag {
|
||||
// Variant order is significant!
|
||||
End,
|
||||
Byte,
|
||||
Short,
|
||||
Int,
|
||||
Long,
|
||||
Float,
|
||||
Double,
|
||||
ByteArray,
|
||||
String,
|
||||
List,
|
||||
Compound,
|
||||
IntArray,
|
||||
LongArray,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn element_type(value: &Value) -> Self {
|
||||
match value {
|
||||
Value::Byte(_) => Tag::Byte,
|
||||
Value::Short(_) => Tag::Short,
|
||||
Value::Int(_) => Tag::Int,
|
||||
Value::Long(_) => Tag::Long,
|
||||
Value::Float(_) => Tag::Float,
|
||||
Value::Double(_) => Tag::Double,
|
||||
Value::ByteArray(_) => Tag::ByteArray,
|
||||
Value::String(_) => Tag::String,
|
||||
Value::List(_) => Tag::List,
|
||||
Value::Compound(_) => Tag::Compound,
|
||||
Value::IntArray(_) => Tag::IntArray,
|
||||
Value::LongArray(_) => Tag::LongArray,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Tag::End => "end",
|
||||
Tag::Byte => "byte",
|
||||
Tag::Short => "short",
|
||||
Tag::Int => "int",
|
||||
Tag::Long => "long",
|
||||
Tag::Float => "float",
|
||||
Tag::Double => "double",
|
||||
Tag::ByteArray => "byte array",
|
||||
Tag::String => "string",
|
||||
Tag::List => "list",
|
||||
Tag::Compound => "compound",
|
||||
Tag::IntArray => "int array",
|
||||
Tag::LongArray => "long array",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Tag {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
166
valence_nbt/src/tests.rs
Normal file
166
valence_nbt/src/tests.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
use std::mem;
|
||||
|
||||
use crate::tag::Tag;
|
||||
use crate::{compound, from_binary_slice, to_binary_writer, Compound, List, Value};
|
||||
|
||||
const ROOT_NAME: &str = "The root name‽";
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
let compound = example_compound();
|
||||
|
||||
to_binary_writer(&mut buf, &compound, ROOT_NAME).unwrap();
|
||||
|
||||
println!("{buf:?}");
|
||||
|
||||
let (decoded, root_name) = from_binary_slice(&mut buf.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(root_name, ROOT_NAME);
|
||||
assert_eq!(compound, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_min_sizes() {
|
||||
fn check(min_val: Value, expected_size: usize) {
|
||||
/// TAG_Compound + root name + field tag + field name + TAG_End
|
||||
const COMPOUND_OVERHEAD: usize = 1 + 2 + 1 + 2 + 1;
|
||||
|
||||
let dbg = format!("{min_val:?}");
|
||||
let mut buf = Vec::new();
|
||||
|
||||
to_binary_writer(&mut buf, &compound!("" => min_val), "").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expected_size,
|
||||
buf.len() - COMPOUND_OVERHEAD,
|
||||
"size mismatch for {dbg}"
|
||||
);
|
||||
}
|
||||
|
||||
check(Value::Byte(0), 1);
|
||||
check(Value::Short(0), 2);
|
||||
check(Value::Int(0), 4);
|
||||
check(Value::Long(0), 8);
|
||||
check(Value::Float(0.0), 4);
|
||||
check(Value::Double(0.0), 8);
|
||||
check(Value::ByteArray([].into()), 4);
|
||||
check(Value::String("".into()), 2);
|
||||
check(Value::List(Vec::<i32>::new().into()), 5);
|
||||
check(Value::Compound(compound!()), 1);
|
||||
check(Value::IntArray([].into()), 4);
|
||||
check(Value::LongArray([].into()), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deeply_nested_compound_encode() {
|
||||
let mut c = compound!("" => 111_i8);
|
||||
for _ in 0..10_000 {
|
||||
c = compound!("" => c);
|
||||
}
|
||||
|
||||
// Should not overflow the stack
|
||||
let _ = to_binary_writer(&mut Vec::new(), &c, ROOT_NAME);
|
||||
|
||||
// Don"t overflow the stack while dropping.
|
||||
mem::forget(c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deeply_nested_compound_decode() {
|
||||
let mut buf = vec![Tag::Compound as u8, 0, 0]; // Root compound
|
||||
let n = 10_000;
|
||||
|
||||
for _ in 0..n {
|
||||
buf.extend([Tag::Compound as u8, 0, 0]);
|
||||
}
|
||||
|
||||
buf.extend((0..n).map(|_| Tag::End as u8));
|
||||
|
||||
buf.push(Tag::End as u8); // End root compound
|
||||
|
||||
// Should not overflow the stack
|
||||
let _ = from_binary_slice(&mut buf.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deeply_nested_list_encode() {
|
||||
let mut l = List::Byte(Vec::new());
|
||||
for _ in 0..10_000 {
|
||||
l = List::List(vec![l]);
|
||||
}
|
||||
|
||||
let c = compound!("" => l);
|
||||
|
||||
// Should not panic
|
||||
let _ = to_binary_writer(&mut Vec::new(), &c, ROOT_NAME);
|
||||
|
||||
// Don"t overflow the stack while dropping.
|
||||
mem::forget(c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deeply_nested_list_decode() {
|
||||
// Root compound with one field.
|
||||
let mut buf = vec![Tag::Compound as u8, 0, 0, Tag::List as u8, 0, 0];
|
||||
let n = 10_000;
|
||||
|
||||
for _ in 0..n - 1 {
|
||||
buf.extend([Tag::List as u8, 0, 0, 0, 1]); // List of list
|
||||
}
|
||||
|
||||
// Last list is an empty list of bytes.
|
||||
buf.extend([Tag::Byte as u8, 0, 0, 0, 0]);
|
||||
|
||||
buf.push(Tag::End as u8); // End root compound
|
||||
|
||||
// Should not overflow the stack
|
||||
let _ = from_binary_slice(&mut buf.as_slice());
|
||||
}
|
||||
|
||||
#[cfg(feature = "preserve_order")]
|
||||
#[test]
|
||||
fn preserves_order() {
|
||||
let letters = ["g", "b", "d", "e", "h", "z", "m", "a", "q"];
|
||||
|
||||
let mut c = Compound::new();
|
||||
for l in letters {
|
||||
c.insert(l, 0_i8);
|
||||
}
|
||||
|
||||
for (k, l) in c.keys().zip(letters) {
|
||||
assert_eq!(k, l);
|
||||
}
|
||||
}
|
||||
|
||||
fn example_compound() -> Compound {
|
||||
fn inner() -> Compound {
|
||||
compound! {
|
||||
"int" => i32::MIN,
|
||||
"long" => i64::MAX,
|
||||
"float" => 1e10_f32,
|
||||
"double" => f64::INFINITY,
|
||||
}
|
||||
}
|
||||
|
||||
compound! {
|
||||
"byte" => 123_i8,
|
||||
"list_of_int" => List::Int(vec![3, -7, 5]),
|
||||
"list_of_string" => List::String(vec![
|
||||
"foo".to_owned(),
|
||||
"bar".to_owned(),
|
||||
"baz".to_owned()
|
||||
]),
|
||||
"string" => "aé日",
|
||||
"compound" => inner(),
|
||||
"list_of_compound" => List::Compound(vec![
|
||||
inner(),
|
||||
inner(),
|
||||
inner(),
|
||||
]),
|
||||
"int_array" => vec![5, -9, i32::MIN, 0, i32::MAX],
|
||||
"byte_array" => vec![0_i8, 2, 3],
|
||||
"long_array" => vec![123_i64, 456, 789],
|
||||
}
|
||||
}
|
230
valence_nbt/src/to_binary_writer.rs
Normal file
230
valence_nbt/src/to_binary_writer.rs
Normal file
|
@ -0,0 +1,230 @@
|
|||
use std::io::Write;
|
||||
|
||||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use zerocopy::AsBytes;
|
||||
|
||||
use crate::tag::Tag;
|
||||
use crate::{Compound, Error, List, Result, Value, MAX_DEPTH};
|
||||
|
||||
/// Encodes uncompressed NBT binary data to the provided writer.
|
||||
///
|
||||
/// Only compounds are permitted at the top level. This is why the function
|
||||
/// accepts a [`Compound`] reference rather than a [`Value`].
|
||||
///
|
||||
/// Additionally, the root compound can be given a name. Typically the empty
|
||||
/// string `""` is used.
|
||||
pub fn to_binary_writer<W: Write>(writer: W, compound: &Compound, root_name: &str) -> Result<()> {
|
||||
let mut state = EncodeState { writer, depth: 0 };
|
||||
|
||||
state.write_tag(Tag::Compound)?;
|
||||
state.write_string(root_name)?;
|
||||
state.write_compound(compound)?;
|
||||
|
||||
debug_assert_eq!(state.depth, 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct EncodeState<W> {
|
||||
writer: W,
|
||||
/// Current recursion depth.
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
impl<W: Write> EncodeState<W> {
|
||||
#[inline]
|
||||
fn check_depth<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
|
||||
if self.depth >= MAX_DEPTH {
|
||||
return Err(Error::new_static("reached maximum recursion depth"));
|
||||
}
|
||||
|
||||
self.depth += 1;
|
||||
let res = f(self);
|
||||
self.depth -= 1;
|
||||
res
|
||||
}
|
||||
|
||||
fn write_tag(&mut self, tag: Tag) -> Result<()> {
|
||||
Ok(self.writer.write_u8(tag as u8)?)
|
||||
}
|
||||
|
||||
fn write_value(&mut self, v: &Value) -> Result<()> {
|
||||
match v {
|
||||
Value::Byte(b) => self.write_byte(*b),
|
||||
Value::Short(s) => self.write_short(*s),
|
||||
Value::Int(i) => self.write_int(*i),
|
||||
Value::Long(l) => self.write_long(*l),
|
||||
Value::Float(f) => self.write_float(*f),
|
||||
Value::Double(d) => self.write_double(*d),
|
||||
Value::ByteArray(ba) => self.write_byte_array(ba),
|
||||
Value::String(s) => self.write_string(s),
|
||||
Value::List(l) => self.check_depth(|st| st.write_any_list(l)),
|
||||
Value::Compound(c) => self.check_depth(|st| st.write_compound(c)),
|
||||
Value::IntArray(ia) => self.write_int_array(ia),
|
||||
Value::LongArray(la) => self.write_long_array(la),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte(&mut self, byte: i8) -> Result<()> {
|
||||
Ok(self.writer.write_i8(byte)?)
|
||||
}
|
||||
|
||||
fn write_short(&mut self, short: i16) -> Result<()> {
|
||||
Ok(self.writer.write_i16::<BigEndian>(short)?)
|
||||
}
|
||||
|
||||
fn write_int(&mut self, int: i32) -> Result<()> {
|
||||
Ok(self.writer.write_i32::<BigEndian>(int)?)
|
||||
}
|
||||
|
||||
fn write_long(&mut self, long: i64) -> Result<()> {
|
||||
Ok(self.writer.write_i64::<BigEndian>(long)?)
|
||||
}
|
||||
|
||||
fn write_float(&mut self, float: f32) -> Result<()> {
|
||||
Ok(self.writer.write_f32::<BigEndian>(float)?)
|
||||
}
|
||||
|
||||
fn write_double(&mut self, double: f64) -> Result<()> {
|
||||
Ok(self.writer.write_f64::<BigEndian>(double)?)
|
||||
}
|
||||
|
||||
fn write_byte_array(&mut self, bytes: &[i8]) -> Result<()> {
|
||||
match bytes.len().try_into() {
|
||||
Ok(len) => self.write_int(len)?,
|
||||
Err(_) => {
|
||||
return Err(Error::new_owned(format!(
|
||||
"byte array of length {} exceeds maximum of i32::MAX",
|
||||
bytes.len(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.writer.write_all(bytes.as_bytes())?)
|
||||
}
|
||||
|
||||
fn write_string(&mut self, s: &str) -> Result<()> {
|
||||
let s = cesu8::to_java_cesu8(s);
|
||||
|
||||
match s.len().try_into() {
|
||||
Ok(len) => self.writer.write_u16::<BigEndian>(len)?,
|
||||
Err(_) => {
|
||||
return Err(Error::new_owned(format!(
|
||||
"string of length {} exceeds maximum of u16::MAX",
|
||||
s.len()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.writer.write_all(&s)?)
|
||||
}
|
||||
|
||||
fn write_any_list(&mut self, list: &List) -> Result<()> {
|
||||
match list {
|
||||
List::Byte(bl) => {
|
||||
self.write_tag(Tag::Byte)?;
|
||||
|
||||
match bl.len().try_into() {
|
||||
Ok(len) => self.write_int(len)?,
|
||||
Err(_) => {
|
||||
return Err(Error::new_owned(format!(
|
||||
"byte list of length {} exceeds maximum of i32::MAX",
|
||||
bl.len(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.writer.write_all(bl.as_bytes())?)
|
||||
}
|
||||
List::Short(sl) => self.write_list(sl, Tag::Short, |st, s| st.write_short(*s)),
|
||||
List::Int(il) => self.write_list(il, Tag::Int, |st, i| st.write_int(*i)),
|
||||
List::Long(ll) => self.write_list(ll, Tag::Long, |st, l| st.write_long(*l)),
|
||||
List::Float(fl) => self.write_list(fl, Tag::Float, |st, f| st.write_float(*f)),
|
||||
List::Double(dl) => self.write_list(dl, Tag::Double, |st, d| st.write_double(*d)),
|
||||
List::ByteArray(bal) => {
|
||||
self.write_list(bal, Tag::ByteArray, |st, ba| st.write_byte_array(ba))
|
||||
}
|
||||
List::String(sl) => self.write_list(sl, Tag::String, |st, s| st.write_string(s)),
|
||||
List::List(ll) => {
|
||||
self.check_depth(|st| st.write_list(ll, Tag::List, |st, l| st.write_any_list(l)))
|
||||
}
|
||||
List::Compound(cl) => self
|
||||
.check_depth(|st| st.write_list(cl, Tag::Compound, |st, c| st.write_compound(c))),
|
||||
List::IntArray(ial) => {
|
||||
self.write_list(ial, Tag::IntArray, |st, ia| st.write_int_array(ia))
|
||||
}
|
||||
List::LongArray(lal) => {
|
||||
self.write_list(lal, Tag::LongArray, |st, la| st.write_long_array(la))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_list<T, F>(&mut self, list: &Vec<T>, elem_type: Tag, mut write_elem: F) -> Result<()>
|
||||
where
|
||||
F: FnMut(&mut Self, &T) -> Result<()>,
|
||||
{
|
||||
self.write_tag(elem_type)?;
|
||||
|
||||
match list.len().try_into() {
|
||||
Ok(len) => self.writer.write_i32::<BigEndian>(len)?,
|
||||
Err(_) => {
|
||||
return Err(Error::new_owned(format!(
|
||||
"{elem_type} list of length {} exceeds maximum of i32::MAX",
|
||||
list.len(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
for elem in list {
|
||||
write_elem(self, elem)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_compound(&mut self, c: &Compound) -> Result<()> {
|
||||
for (k, v) in c.iter() {
|
||||
self.write_tag(Tag::element_type(v))?;
|
||||
self.write_string(k)?;
|
||||
self.write_value(v)?;
|
||||
}
|
||||
self.write_tag(Tag::End)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_int_array(&mut self, ia: &[i32]) -> Result<()> {
|
||||
match ia.len().try_into() {
|
||||
Ok(len) => self.write_int(len)?,
|
||||
Err(_) => {
|
||||
return Err(Error::new_owned(format!(
|
||||
"int array of length {} exceeds maximum of i32::MAX",
|
||||
ia.len(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
for i in ia {
|
||||
self.write_int(*i)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_long_array(&mut self, la: &[i64]) -> Result<()> {
|
||||
match la.len().try_into() {
|
||||
Ok(len) => self.write_int(len)?,
|
||||
Err(_) => {
|
||||
return Err(Error::new_owned(format!(
|
||||
"long array of length {} exceeds maximum of i32::MAX",
|
||||
la.len(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
for l in la {
|
||||
self.write_long(*l)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
236
valence_nbt/src/value.rs
Normal file
236
valence_nbt/src/value.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::Compound;
|
||||
|
||||
/// Represents an arbitrary NBT value.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Value {
|
||||
Byte(i8),
|
||||
Short(i16),
|
||||
Int(i32),
|
||||
Long(i64),
|
||||
Float(f32),
|
||||
Double(f64),
|
||||
ByteArray(Vec<i8>),
|
||||
String(String),
|
||||
List(List),
|
||||
Compound(Compound),
|
||||
IntArray(Vec<i32>),
|
||||
LongArray(Vec<i64>),
|
||||
}
|
||||
|
||||
/// An NBT list value.
|
||||
///
|
||||
/// NBT lists are homogeneous, meaning each list element must be of the same
|
||||
/// type. This is opposed to a format like JSON where lists can be
|
||||
/// heterogeneous. Here is a JSON list that would be illegal in NBT:
|
||||
///
|
||||
/// ```json
|
||||
/// [42, "hello", {}]
|
||||
/// ```
|
||||
///
|
||||
/// Every possible element type has its own variant in this enum. As a result,
|
||||
/// heterogeneous lists are unrepresentable.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum List {
|
||||
Byte(Vec<i8>),
|
||||
Short(Vec<i16>),
|
||||
Int(Vec<i32>),
|
||||
Long(Vec<i64>),
|
||||
Float(Vec<f32>),
|
||||
Double(Vec<f64>),
|
||||
ByteArray(Vec<Vec<i8>>),
|
||||
String(Vec<String>),
|
||||
List(Vec<List>),
|
||||
Compound(Vec<Compound>),
|
||||
IntArray(Vec<Vec<i32>>),
|
||||
LongArray(Vec<Vec<i64>>),
|
||||
}
|
||||
|
||||
impl List {
|
||||
/// Returns the length of this list.
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
List::Byte(l) => l.len(),
|
||||
List::Short(l) => l.len(),
|
||||
List::Int(l) => l.len(),
|
||||
List::Long(l) => l.len(),
|
||||
List::Float(l) => l.len(),
|
||||
List::Double(l) => l.len(),
|
||||
List::ByteArray(l) => l.len(),
|
||||
List::String(l) => l.len(),
|
||||
List::List(l) => l.len(),
|
||||
List::Compound(l) => l.len(),
|
||||
List::IntArray(l) => l.len(),
|
||||
List::LongArray(l) => l.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this list has no elements. `false` otherwise.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for Value {
|
||||
fn from(v: i8) -> Self {
|
||||
Self::Byte(v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Bools are usually represented as `0` or `1` bytes in NBT.
|
||||
impl From<bool> for Value {
|
||||
fn from(b: bool) -> Self {
|
||||
Value::Byte(b as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for Value {
|
||||
fn from(v: i16) -> Self {
|
||||
Self::Short(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Value {
|
||||
fn from(v: i32) -> Self {
|
||||
Self::Int(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
fn from(v: i64) -> Self {
|
||||
Self::Long(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Value {
|
||||
fn from(v: f32) -> Self {
|
||||
Self::Float(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(v: f64) -> Self {
|
||||
Self::Double(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i8>> for Value {
|
||||
fn from(v: Vec<i8>) -> Self {
|
||||
Self::ByteArray(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
fn from(v: String) -> Self {
|
||||
Self::String(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Value {
|
||||
fn from(v: &'a str) -> Self {
|
||||
Self::String(v.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Cow<'a, str>> for Value {
|
||||
fn from(v: Cow<'a, str>) -> Self {
|
||||
Self::String(v.into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<List> for Value {
|
||||
fn from(v: List) -> Self {
|
||||
Self::List(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Compound> for Value {
|
||||
fn from(v: Compound) -> Self {
|
||||
Self::Compound(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i32>> for Value {
|
||||
fn from(v: Vec<i32>) -> Self {
|
||||
Self::IntArray(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i64>> for Value {
|
||||
fn from(v: Vec<i64>) -> Self {
|
||||
Self::LongArray(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i8>> for List {
|
||||
fn from(v: Vec<i8>) -> Self {
|
||||
List::Byte(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i16>> for List {
|
||||
fn from(v: Vec<i16>) -> Self {
|
||||
List::Short(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i32>> for List {
|
||||
fn from(v: Vec<i32>) -> Self {
|
||||
List::Int(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<i64>> for List {
|
||||
fn from(v: Vec<i64>) -> Self {
|
||||
List::Long(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<f32>> for List {
|
||||
fn from(v: Vec<f32>) -> Self {
|
||||
List::Float(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<f64>> for List {
|
||||
fn from(v: Vec<f64>) -> Self {
|
||||
List::Double(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Vec<i8>>> for List {
|
||||
fn from(v: Vec<Vec<i8>>) -> Self {
|
||||
List::ByteArray(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<String>> for List {
|
||||
fn from(v: Vec<String>) -> Self {
|
||||
List::String(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<List>> for List {
|
||||
fn from(v: Vec<List>) -> Self {
|
||||
List::List(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Compound>> for List {
|
||||
fn from(v: Vec<Compound>) -> Self {
|
||||
List::Compound(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Vec<i32>>> for List {
|
||||
fn from(v: Vec<Vec<i32>>) -> Self {
|
||||
List::IntArray(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Vec<i64>>> for List {
|
||||
fn from(v: Vec<Vec<i64>>) -> Self {
|
||||
List::LongArray(v)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue