mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Move to own NBT crate (Mostly) (#100)
* Remove fastnbt as dependency * Move to own NBT crate * Added deserialization Also replaced i8 bool's with bool's * Use: fastnbt for pumpkin-world Until i don't figure out why Arrays do not work, we can use fastnbt
This commit is contained in:
committed by
GitHub
parent
e83e5d7df8
commit
402f3951f5
@@ -41,6 +41,8 @@ thiserror = "2"
|
||||
num-traits = "0.2"
|
||||
num-derive = "0.4"
|
||||
|
||||
bytes = "1.8"
|
||||
|
||||
# Concurrency/Parallelism and Synchronization
|
||||
rayon = "1.10.0"
|
||||
parking_lot = { version = "0.12.3", features = ["send_guard"] }
|
||||
|
||||
26665
assets/items.json
26665
assets/items.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
17864
assets/tags.json
17864
assets/tags.json
File diff suppressed because it is too large
Load Diff
@@ -4,12 +4,12 @@ version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
serde.workspace = true
|
||||
uuid.workspace = true
|
||||
num-traits.workspace = true
|
||||
num-derive.workspace = true
|
||||
|
||||
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
||||
colored = "2"
|
||||
md5 = "0.7.0"
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::borrow::Cow;
|
||||
use click::ClickEvent;
|
||||
use color::Color;
|
||||
use colored::Colorize;
|
||||
use fastnbt::SerOpts;
|
||||
use hover::HoverEvent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use style::Style;
|
||||
@@ -113,31 +112,31 @@ impl<'a> TextComponent<'a> {
|
||||
|
||||
/// Makes the text bold
|
||||
pub fn bold(mut self) -> Self {
|
||||
self.style.bold = Some(1);
|
||||
self.style.bold = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text italic
|
||||
pub fn italic(mut self) -> Self {
|
||||
self.style.italic = Some(1);
|
||||
self.style.italic = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text underlined
|
||||
pub fn underlined(mut self) -> Self {
|
||||
self.style.underlined = Some(1);
|
||||
self.style.underlined = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text strikethrough
|
||||
pub fn strikethrough(mut self) -> Self {
|
||||
self.style.strikethrough = Some(1);
|
||||
self.style.strikethrough = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text obfuscated
|
||||
pub fn obfuscated(mut self) -> Self {
|
||||
self.style.obfuscated = Some(1);
|
||||
self.style.obfuscated = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -175,7 +174,10 @@ impl<'a> TextComponent<'a> {
|
||||
};
|
||||
// dbg!(&serde_json::to_string(&astruct));
|
||||
|
||||
fastnbt::to_bytes_with_opts(&astruct, SerOpts::network_nbt()).unwrap()
|
||||
// TODO
|
||||
pumpkin_nbt::serializer::to_bytes_unnamed(&astruct)
|
||||
.unwrap()
|
||||
.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,23 +13,19 @@ pub struct Style<'a> {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub color: Option<Color>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub bold: Option<u8>,
|
||||
pub bold: Option<bool>,
|
||||
/// Whether to render the content in italic.
|
||||
/// Keep in mind that booleans are represented as bytes in nbt
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub italic: Option<u8>,
|
||||
pub italic: Option<bool>,
|
||||
/// Whether to render the content in underlined.
|
||||
/// Keep in mind that booleans are represented as bytes in nbt
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub underlined: Option<u8>,
|
||||
pub underlined: Option<bool>,
|
||||
/// Whether to render the content in strikethrough.
|
||||
/// Keep in mind that booleans are represented as bytes in nbt
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub strikethrough: Option<u8>,
|
||||
pub strikethrough: Option<bool>,
|
||||
/// Whether to render the content in obfuscated.
|
||||
/// Keep in mind that booleans are represented as bytes in nbt
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub obfuscated: Option<u8>,
|
||||
pub obfuscated: Option<bool>,
|
||||
/// When the text is shift-clicked by a player, this string is inserted in their chat input. It does not overwrite any existing text the player was writing. This only works in chat messages
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub insertion: Option<String>,
|
||||
@@ -54,31 +50,31 @@ impl<'a> Style<'a> {
|
||||
|
||||
/// Makes the text bold
|
||||
pub fn bold(mut self) -> Self {
|
||||
self.bold = Some(1);
|
||||
self.bold = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text italic
|
||||
pub fn italic(mut self) -> Self {
|
||||
self.italic = Some(1);
|
||||
self.italic = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text underlined
|
||||
pub fn underlined(mut self) -> Self {
|
||||
self.underlined = Some(1);
|
||||
self.underlined = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text strikethrough
|
||||
pub fn strikethrough(mut self) -> Self {
|
||||
self.strikethrough = Some(1);
|
||||
self.strikethrough = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the text obfuscated
|
||||
pub fn obfuscated(mut self) -> Self {
|
||||
self.obfuscated = Some(1);
|
||||
self.obfuscated = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
10
pumpkin-nbt/Cargo.toml
Normal file
10
pumpkin-nbt/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "pumpkin-nbt"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
bytes.workspace = true
|
||||
cesu8 = "1.1.0"
|
||||
163
pumpkin-nbt/src/compound.rs
Normal file
163
pumpkin-nbt/src/compound.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use crate::tag::NbtTag;
|
||||
use crate::{get_nbt_string, Error, Nbt, END_ID};
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use std::io::{Cursor, Write};
|
||||
use std::vec::IntoIter;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
|
||||
pub struct NbtCompound {
|
||||
pub child_tags: Vec<(String, NbtTag)>,
|
||||
}
|
||||
|
||||
impl NbtCompound {
|
||||
pub fn new() -> NbtCompound {
|
||||
NbtCompound {
|
||||
child_tags: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_content(bytes: &mut impl Buf) -> Result<NbtCompound, Error> {
|
||||
let mut compound = NbtCompound::new();
|
||||
|
||||
while bytes.has_remaining() {
|
||||
let tag_id = bytes.get_u8();
|
||||
if tag_id == END_ID {
|
||||
break;
|
||||
}
|
||||
|
||||
let name = get_nbt_string(bytes).map_err(|_| Error::Cesu8DecodingError)?;
|
||||
|
||||
if let Ok(tag) = NbtTag::deserialize_data(bytes, tag_id) {
|
||||
compound.put(name, tag);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(compound)
|
||||
}
|
||||
|
||||
pub fn deserialize_content_from_cursor(
|
||||
cursor: &mut Cursor<&[u8]>,
|
||||
) -> Result<NbtCompound, Error> {
|
||||
Self::deserialize_content(cursor)
|
||||
}
|
||||
|
||||
pub fn serialize_content(&self) -> Bytes {
|
||||
let mut bytes = BytesMut::new();
|
||||
for (name, tag) in &self.child_tags {
|
||||
bytes.put_u8(tag.get_type_id());
|
||||
bytes.put(NbtTag::String(name.clone()).serialize_data());
|
||||
bytes.put(tag.serialize_data());
|
||||
}
|
||||
bytes.put_u8(END_ID);
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
pub fn serialize_content_to_writer<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
|
||||
writer.write_all(&self.serialize_content())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put(&mut self, name: String, value: impl Into<NbtTag>) {
|
||||
if !self.child_tags.iter().any(|(key, _)| key == &name) {
|
||||
self.child_tags.push((name, value.into()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_byte(&self, name: &str) -> Option<i8> {
|
||||
self.get(name).and_then(|tag| tag.extract_byte())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, name: &str) -> Option<&NbtTag> {
|
||||
for (key, value) in &self.child_tags {
|
||||
if key.as_str() == name {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_short(&self, name: &str) -> Option<i16> {
|
||||
self.get(name).and_then(|tag| tag.extract_short())
|
||||
}
|
||||
|
||||
pub fn get_int(&self, name: &str) -> Option<i32> {
|
||||
self.get(name).and_then(|tag| tag.extract_int())
|
||||
}
|
||||
|
||||
pub fn get_long(&self, name: &str) -> Option<i64> {
|
||||
self.get(name).and_then(|tag| tag.extract_long())
|
||||
}
|
||||
|
||||
pub fn get_float(&self, name: &str) -> Option<f32> {
|
||||
self.get(name).and_then(|tag| tag.extract_float())
|
||||
}
|
||||
|
||||
pub fn get_double(&self, name: &str) -> Option<f64> {
|
||||
self.get(name).and_then(|tag| tag.extract_double())
|
||||
}
|
||||
|
||||
pub fn get_bool(&self, name: &str) -> Option<bool> {
|
||||
self.get(name).and_then(|tag| tag.extract_bool())
|
||||
}
|
||||
|
||||
pub fn get_string(&self, name: &str) -> Option<&String> {
|
||||
self.get(name).and_then(|tag| tag.extract_string())
|
||||
}
|
||||
|
||||
pub fn get_list(&self, name: &str) -> Option<&Vec<NbtTag>> {
|
||||
self.get(name).and_then(|tag| tag.extract_list())
|
||||
}
|
||||
|
||||
pub fn get_compound(&self, name: &str) -> Option<&NbtCompound> {
|
||||
self.get(name).and_then(|tag| tag.extract_compound())
|
||||
}
|
||||
|
||||
pub fn get_int_array(&self, name: &str) -> Option<&Vec<i32>> {
|
||||
self.get(name).and_then(|tag| tag.extract_int_array())
|
||||
}
|
||||
|
||||
pub fn get_long_array(&self, name: &str) -> Option<&Vec<i64>> {
|
||||
self.get(name).and_then(|tag| tag.extract_long_array())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Nbt> for NbtCompound {
|
||||
fn from(value: Nbt) -> Self {
|
||||
value.root_tag
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, NbtTag)> for NbtCompound {
|
||||
fn from_iter<T: IntoIterator<Item = (String, NbtTag)>>(iter: T) -> Self {
|
||||
let mut compound = NbtCompound::new();
|
||||
for (key, value) in iter {
|
||||
compound.put(key, value);
|
||||
}
|
||||
compound
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for NbtCompound {
|
||||
type Item = (String, NbtTag);
|
||||
type IntoIter = IntoIter<(String, NbtTag)>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.child_tags.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(String, NbtTag)> for NbtCompound {
|
||||
fn extend<T: IntoIterator<Item = (String, NbtTag)>>(&mut self, iter: T) {
|
||||
self.child_tags.extend(iter)
|
||||
}
|
||||
}
|
||||
|
||||
// Rust's AsRef is currently not reflexive so we need to implement it manually
|
||||
impl AsRef<NbtCompound> for NbtCompound {
|
||||
fn as_ref(&self) -> &NbtCompound {
|
||||
self
|
||||
}
|
||||
}
|
||||
212
pumpkin-nbt/src/deserializer.rs
Normal file
212
pumpkin-nbt/src/deserializer.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
use crate::*;
|
||||
use bytes::{Buf, BytesMut};
|
||||
use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor};
|
||||
use serde::{forward_to_deserialize_any, Deserialize};
|
||||
use std::io::Cursor;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Deserializer<'de, T> {
|
||||
input: &'de mut T,
|
||||
tag_to_deserialize: Option<u8>,
|
||||
is_named: bool,
|
||||
}
|
||||
|
||||
impl<'de, T: Buf> Deserializer<'de, T> {
|
||||
pub fn new(input: &'de mut T, is_named: bool) -> Self {
|
||||
Deserializer {
|
||||
input,
|
||||
tag_to_deserialize: None,
|
||||
is_named,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes struct using Serde Deserializer from unnamed (network) NBT
|
||||
pub fn from_bytes<'a, T>(s: &'a mut BytesMut) -> Result<T>
|
||||
where
|
||||
T: Deserialize<'a>,
|
||||
{
|
||||
let mut deserializer = Deserializer::new(s, true);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
pub fn from_cursor<'a, T>(cursor: &'a mut Cursor<&[u8]>) -> Result<T>
|
||||
where
|
||||
T: Deserialize<'a>,
|
||||
{
|
||||
let mut deserializer = Deserializer::new(cursor, true);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
/// Deserializes struct using Serde Deserializer from normal NBT
|
||||
pub fn from_bytes_unnamed<'a, T>(s: &'a mut BytesMut) -> Result<T>
|
||||
where
|
||||
T: Deserialize<'a>,
|
||||
{
|
||||
let mut deserializer = Deserializer::new(s, false);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
pub fn from_cursor_unnamed<'a, T>(cursor: &'a mut Cursor<&[u8]>) -> Result<T>
|
||||
where
|
||||
T: Deserialize<'a>,
|
||||
{
|
||||
let mut deserializer = Deserializer::new(cursor, false);
|
||||
T::deserialize(&mut deserializer)
|
||||
}
|
||||
|
||||
impl<'de, 'a, T: Buf> de::Deserializer<'de> for &'a mut Deserializer<'de, T> {
|
||||
type Error = Error;
|
||||
|
||||
forward_to_deserialize_any!(i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 seq char str string bytes byte_buf tuple tuple_struct enum ignored_any unit unit_struct option newtype_struct);
|
||||
|
||||
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let tag_to_deserialize = self.tag_to_deserialize.unwrap();
|
||||
|
||||
let list_type = match tag_to_deserialize {
|
||||
LIST_ID => Some(self.input.get_u8()),
|
||||
INT_ARRAY_ID => Some(INT_ID),
|
||||
LONG_ARRAY_ID => Some(LONG_ID),
|
||||
BYTE_ARRAY_ID => Some(BYTE_ID),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(list_type) = list_type {
|
||||
let remaining_values = self.input.get_u32();
|
||||
return visitor.visit_seq(ListAccess {
|
||||
de: self,
|
||||
list_type,
|
||||
remaining_values,
|
||||
});
|
||||
}
|
||||
|
||||
let result: Result<V::Value> = Ok(
|
||||
match NbtTag::deserialize_data(self.input, tag_to_deserialize)? {
|
||||
NbtTag::Byte(value) => visitor.visit_i8::<Error>(value)?,
|
||||
NbtTag::Short(value) => visitor.visit_i16::<Error>(value)?,
|
||||
NbtTag::Int(value) => visitor.visit_i32::<Error>(value)?,
|
||||
NbtTag::Long(value) => visitor.visit_i64::<Error>(value)?,
|
||||
NbtTag::Float(value) => visitor.visit_f32::<Error>(value)?,
|
||||
NbtTag::Double(value) => visitor.visit_f64::<Error>(value)?,
|
||||
NbtTag::String(value) => visitor.visit_string::<Error>(value)?,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
);
|
||||
self.tag_to_deserialize = None;
|
||||
result
|
||||
}
|
||||
|
||||
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.tag_to_deserialize.unwrap() == BYTE_ID {
|
||||
let value = self.input.get_u8();
|
||||
if value != 0 {
|
||||
return visitor.visit_bool(true);
|
||||
}
|
||||
}
|
||||
visitor.visit_bool(false)
|
||||
}
|
||||
|
||||
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.tag_to_deserialize.is_none() {
|
||||
let next_byte = self.input.get_u8();
|
||||
if next_byte != COMPOUND_ID {
|
||||
return Err(Error::NoRootCompound(next_byte));
|
||||
}
|
||||
|
||||
if self.is_named {
|
||||
// Consume struct name
|
||||
NbtTag::deserialize(self.input)?;
|
||||
}
|
||||
}
|
||||
|
||||
let value = visitor.visit_map(CompoundAccess { de: self })?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn deserialize_struct<V>(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_fields: &'static [&'static str],
|
||||
visitor: V,
|
||||
) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
self.deserialize_map(visitor)
|
||||
}
|
||||
|
||||
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let str = get_nbt_string(&mut self.input).map_err(|_| Error::Cesu8DecodingError)?;
|
||||
visitor.visit_string(str)
|
||||
}
|
||||
|
||||
fn is_human_readable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct CompoundAccess<'a, 'de: 'a, T: Buf> {
|
||||
de: &'a mut Deserializer<'de, T>,
|
||||
}
|
||||
|
||||
impl<'de, 'a, T: Buf> MapAccess<'de> for CompoundAccess<'a, 'de, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
|
||||
where
|
||||
K: DeserializeSeed<'de>,
|
||||
{
|
||||
let tag = self.de.input.get_u8();
|
||||
self.de.tag_to_deserialize = Some(tag);
|
||||
|
||||
if tag == END_ID {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
seed.deserialize(&mut *self.de).map(Some)
|
||||
}
|
||||
|
||||
fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
|
||||
where
|
||||
V: DeserializeSeed<'de>,
|
||||
{
|
||||
seed.deserialize(&mut *self.de)
|
||||
}
|
||||
}
|
||||
|
||||
struct ListAccess<'a, 'de: 'a, T: Buf> {
|
||||
de: &'a mut Deserializer<'de, T>,
|
||||
remaining_values: u32,
|
||||
list_type: u8,
|
||||
}
|
||||
|
||||
impl<'a, 'de, T: Buf> SeqAccess<'de> for ListAccess<'a, 'de, T> {
|
||||
type Error = Error;
|
||||
|
||||
fn next_element_seed<E>(&mut self, seed: E) -> Result<Option<E::Value>>
|
||||
where
|
||||
E: DeserializeSeed<'de>,
|
||||
{
|
||||
if self.remaining_values == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
self.remaining_values -= 1;
|
||||
self.de.tag_to_deserialize = Some(self.list_type);
|
||||
seed.deserialize(&mut *self.de).map(Some)
|
||||
}
|
||||
}
|
||||
204
pumpkin-nbt/src/lib.rs
Normal file
204
pumpkin-nbt/src/lib.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{self, Cursor, Write},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use cesu8::Cesu8DecodingError;
|
||||
use compound::NbtCompound;
|
||||
use serde::{de, ser};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use tag::NbtTag;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod compound;
|
||||
pub mod deserializer;
|
||||
pub mod serializer;
|
||||
pub mod tag;
|
||||
|
||||
// This NBT crate is inspired from CrabNBT
|
||||
|
||||
pub const END_ID: u8 = 0;
|
||||
pub const BYTE_ID: u8 = 1;
|
||||
pub const SHORT_ID: u8 = 2;
|
||||
pub const INT_ID: u8 = 3;
|
||||
pub const LONG_ID: u8 = 4;
|
||||
pub const FLOAT_ID: u8 = 5;
|
||||
pub const DOUBLE_ID: u8 = 6;
|
||||
pub const BYTE_ARRAY_ID: u8 = 7;
|
||||
pub const STRING_ID: u8 = 8;
|
||||
pub const LIST_ID: u8 = 9;
|
||||
pub const COMPOUND_ID: u8 = 10;
|
||||
pub const INT_ARRAY_ID: u8 = 11;
|
||||
pub const LONG_ARRAY_ID: u8 = 12;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("The root tag of the NBT file is not a compound tag. Received tag id: {0}")]
|
||||
NoRootCompound(u8),
|
||||
#[error("Encountered an unknown NBT tag id {0}.")]
|
||||
UnknownTagId(u8),
|
||||
#[error("Failed to Cesu 8 Decode")]
|
||||
Cesu8DecodingError,
|
||||
#[error("Serde error: {0}")]
|
||||
SerdeError(String),
|
||||
#[error("NBT doesn't support this type {0}")]
|
||||
UnsupportedType(String),
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T: Display>(msg: T) -> Self {
|
||||
Error::SerdeError(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl de::Error for Error {
|
||||
fn custom<T: Display>(msg: T) -> Self {
|
||||
Error::SerdeError(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, PartialOrd)]
|
||||
pub struct Nbt {
|
||||
pub name: String,
|
||||
pub root_tag: NbtCompound,
|
||||
}
|
||||
|
||||
impl Nbt {
|
||||
pub fn new(name: String, tag: NbtCompound) -> Self {
|
||||
Nbt {
|
||||
name,
|
||||
root_tag: tag,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(bytes: &mut impl Buf) -> Result<Nbt, Error> {
|
||||
let tag_type_id = bytes.get_u8();
|
||||
|
||||
if tag_type_id != COMPOUND_ID {
|
||||
return Err(Error::NoRootCompound(tag_type_id));
|
||||
}
|
||||
|
||||
Ok(Nbt {
|
||||
name: get_nbt_string(bytes).map_err(|_| Error::Cesu8DecodingError)?,
|
||||
root_tag: NbtCompound::deserialize_content(bytes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
|
||||
Self::read(cursor)
|
||||
}
|
||||
|
||||
/// Reads NBT tag, that doesn't contain the name of root compound.
|
||||
/// Used in [Network NBT](https://wiki.vg/NBT#Network_NBT_(Java_Edition)).
|
||||
pub fn read_unnamed(bytes: &mut impl Buf) -> Result<Nbt, Error> {
|
||||
let tag_type_id = bytes.get_u8();
|
||||
|
||||
if tag_type_id != COMPOUND_ID {
|
||||
return Err(Error::NoRootCompound(tag_type_id));
|
||||
}
|
||||
|
||||
Ok(Nbt {
|
||||
name: String::new(),
|
||||
root_tag: NbtCompound::deserialize_content(bytes)
|
||||
.map_err(|_| Error::Cesu8DecodingError)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_unnamed_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
|
||||
Self::read_unnamed(cursor)
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Bytes {
|
||||
let mut bytes = BytesMut::new();
|
||||
bytes.put_u8(COMPOUND_ID);
|
||||
bytes.put(NbtTag::String(self.name.to_string()).serialize_data());
|
||||
bytes.put(self.root_tag.serialize_content());
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
pub fn write_to_writer<W: Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_all(&self.write())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes NBT tag, without name of root compound.
|
||||
/// Used in [Network NBT](https://wiki.vg/NBT#Network_NBT_(Java_Edition)).
|
||||
pub fn write_unnamed(&self) -> Bytes {
|
||||
let mut bytes = BytesMut::new();
|
||||
bytes.put_u8(COMPOUND_ID);
|
||||
bytes.put(self.root_tag.serialize_content());
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
pub fn write_unnamed_to_writer<W: Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_all(&self.write_unnamed())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Nbt {
|
||||
type Target = NbtCompound;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.root_tag
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NbtCompound> for Nbt {
|
||||
fn from(value: NbtCompound) -> Self {
|
||||
Nbt::new(String::new(), value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Nbt
|
||||
where
|
||||
T: ?Sized,
|
||||
<Nbt as Deref>::Target: AsRef<T>,
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
self.deref().as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<NbtCompound> for Nbt {
|
||||
fn as_mut(&mut self) -> &mut NbtCompound {
|
||||
&mut self.root_tag
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_nbt_string(bytes: &mut impl Buf) -> Result<String, Cesu8DecodingError> {
|
||||
let len = bytes.get_u16() as usize;
|
||||
let string_bytes = bytes.copy_to_bytes(len);
|
||||
let string = cesu8::from_java_cesu8(&string_bytes)?;
|
||||
Ok(string.to_string())
|
||||
}
|
||||
|
||||
macro_rules! impl_array {
|
||||
($name:ident, $variant:expr) => {
|
||||
pub struct $name;
|
||||
|
||||
impl $name {
|
||||
pub fn serialize<T, S>(input: T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_newtype_variant("nbt_array", 0, $variant, &input)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
T: Deserialize<'de>,
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
T::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_array!(IntArray, "int");
|
||||
impl_array!(LongArray, "long");
|
||||
impl_array!(BytesArray, "byte");
|
||||
430
pumpkin-nbt/src/serializer.rs
Normal file
430
pumpkin-nbt/src/serializer.rs
Normal file
@@ -0,0 +1,430 @@
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::ser::Impossible;
|
||||
use serde::{ser, Serialize};
|
||||
use std::io::Write;
|
||||
|
||||
use crate::tag::NbtTag;
|
||||
use crate::{
|
||||
Error, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID,
|
||||
LIST_ID, LONG_ARRAY_ID, LONG_ID, SHORT_ID, STRING_ID,
|
||||
};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub struct Serializer {
|
||||
output: BytesMut,
|
||||
state: State,
|
||||
}
|
||||
|
||||
// NBT has a different order of things, then most other formats
|
||||
// So I use State, to keep what serializer has to do, and some information like field name
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum State {
|
||||
// In network NBT root name is not present
|
||||
Root(Option<String>),
|
||||
Named(String),
|
||||
// Used by maps, to check if key is String
|
||||
MapKey,
|
||||
FirstListElement { len: i32 },
|
||||
ListElement,
|
||||
Array { name: String, array_type: String },
|
||||
}
|
||||
|
||||
impl Serializer {
|
||||
fn parse_state(&mut self, tag: u8) -> Result<()> {
|
||||
match &mut self.state {
|
||||
State::Named(name) | State::Array { name, .. } => {
|
||||
self.output.put_u8(tag);
|
||||
self.output
|
||||
.put(NbtTag::String(name.clone()).serialize_data());
|
||||
}
|
||||
State::FirstListElement { len } => {
|
||||
self.output.put_u8(tag);
|
||||
self.output.put_i32(*len);
|
||||
}
|
||||
State::MapKey => {
|
||||
if tag != STRING_ID {
|
||||
return Err(Error::SerdeError(format!(
|
||||
"Map key can only be string, not {tag}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
State::ListElement => {}
|
||||
_ => return Err(Error::SerdeError("Invalid Serializer state!".to_string())),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes struct using Serde Serializer to unnamed (network) NBT
|
||||
pub fn to_bytes_unnamed<T>(value: &T) -> Result<BytesMut>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let mut serializer = Serializer {
|
||||
output: BytesMut::new(),
|
||||
state: State::Root(None),
|
||||
};
|
||||
value.serialize(&mut serializer)?;
|
||||
Ok(serializer.output)
|
||||
}
|
||||
|
||||
pub fn to_writer_unnamed<T, W>(value: &T, mut writer: W) -> Result<()>
|
||||
where
|
||||
T: Serialize,
|
||||
W: Write,
|
||||
{
|
||||
writer.write_all(&to_bytes_unnamed(value)?).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes struct using Serde Serializer to normal NBT
|
||||
pub fn to_bytes<T>(value: &T, name: String) -> Result<BytesMut>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let mut serializer = Serializer {
|
||||
output: BytesMut::new(),
|
||||
state: State::Root(Some(name)),
|
||||
};
|
||||
value.serialize(&mut serializer)?;
|
||||
Ok(serializer.output)
|
||||
}
|
||||
|
||||
pub fn to_writer<T, W>(value: &T, name: String, mut writer: W) -> Result<()>
|
||||
where
|
||||
T: Serialize,
|
||||
W: Write,
|
||||
{
|
||||
writer.write_all(&to_bytes(value, name)?).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> ser::Serializer for &'a mut Serializer {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
type SerializeSeq = Self;
|
||||
type SerializeTuple = Impossible<(), Error>;
|
||||
type SerializeTupleStruct = Impossible<(), Error>;
|
||||
type SerializeTupleVariant = Impossible<(), Error>;
|
||||
type SerializeMap = Self;
|
||||
type SerializeStruct = Self;
|
||||
type SerializeStructVariant = Impossible<(), Error>;
|
||||
|
||||
// NBT doesn't have bool type, but it's most commonly represented as a byte
|
||||
fn serialize_bool(self, v: bool) -> Result<()> {
|
||||
self.serialize_i8(v as i8)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i8(self, v: i8) -> Result<()> {
|
||||
self.parse_state(BYTE_ID)?;
|
||||
self.output.put_i8(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i16(self, v: i16) -> Result<()> {
|
||||
self.parse_state(SHORT_ID)?;
|
||||
self.output.put_i16(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i32(self, v: i32) -> Result<()> {
|
||||
self.parse_state(INT_ID)?;
|
||||
self.output.put_i32(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_i64(self, v: i64) -> Result<()> {
|
||||
self.parse_state(LONG_ID)?;
|
||||
self.output.put_i64(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u8(self, v: u8) -> Result<()> {
|
||||
self.parse_state(BYTE_ID)?;
|
||||
self.output.put_u8(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u16(self, v: u16) -> Result<()> {
|
||||
self.parse_state(SHORT_ID)?;
|
||||
self.output.put_u16(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u32(self, v: u32) -> Result<()> {
|
||||
self.parse_state(INT_ID)?;
|
||||
self.output.put_u32(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_u64(self, v: u64) -> Result<()> {
|
||||
self.parse_state(LONG_ID)?;
|
||||
self.output.put_u64(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_f32(self, v: f32) -> Result<()> {
|
||||
self.parse_state(FLOAT_ID)?;
|
||||
self.output.put_f32(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_f64(self, v: f64) -> Result<()> {
|
||||
self.parse_state(DOUBLE_ID)?;
|
||||
self.output.put_f64(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_char(self, _v: char) -> Result<()> {
|
||||
Err(Error::UnsupportedType("char".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<()> {
|
||||
self.parse_state(STRING_ID)?;
|
||||
if self.state == State::MapKey {
|
||||
self.state = State::Named(v.to_string());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.output
|
||||
.put(NbtTag::String(v.to_string()).serialize_data());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<()> {
|
||||
self.parse_state(LIST_ID)?;
|
||||
self.output.put_u8(BYTE_ID);
|
||||
self.output.put_i32(v.len() as i32);
|
||||
self.output.put_slice(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Just skip serializing, if value is none
|
||||
fn serialize_none(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_some<T>(self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
|
||||
Err(Error::UnsupportedType("unit struct".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
) -> Result<()> {
|
||||
self.serialize_str(variant)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T>(self, _name: &'static str, _value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
Err(Error::UnsupportedType("newtype struct".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_newtype_variant<T>(
|
||||
self,
|
||||
name: &'static str,
|
||||
_variant_index: u32,
|
||||
variant: &'static str,
|
||||
value: &T,
|
||||
) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
if name != "nbt_array" {
|
||||
return Err(Error::SerdeError(
|
||||
"new_type variant supports only nbt_array".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let name = match self.state {
|
||||
State::Named(ref name) => name.clone(),
|
||||
_ => return Err(Error::SerdeError("Invalid Serializer state!".to_string())),
|
||||
};
|
||||
|
||||
self.state = State::Array {
|
||||
name,
|
||||
array_type: variant.to_string(),
|
||||
};
|
||||
|
||||
value.serialize(self)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
|
||||
if len.is_none() {
|
||||
return Err(Error::SerdeError(
|
||||
"Length of the sequence must be known first!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
match &mut self.state {
|
||||
State::Array { array_type, .. } => {
|
||||
let id = match array_type.as_str() {
|
||||
"byte" => BYTE_ARRAY_ID,
|
||||
"int" => INT_ARRAY_ID,
|
||||
"long" => LONG_ARRAY_ID,
|
||||
_ => {
|
||||
return Err(Error::SerdeError(
|
||||
"Array supports only byte, int, long".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
self.parse_state(id)?;
|
||||
self.output.put_i32(len.unwrap() as i32);
|
||||
self.state = State::ListElement;
|
||||
}
|
||||
_ => {
|
||||
self.parse_state(LIST_ID)?;
|
||||
self.state = State::FirstListElement {
|
||||
len: len.unwrap() as i32,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
|
||||
Err(Error::UnsupportedType("tuple".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_tuple_struct(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleStruct> {
|
||||
Err(Error::UnsupportedType("tuple struct".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_tuple_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeTupleVariant> {
|
||||
Err(Error::UnsupportedType("tuple variant".to_string()))
|
||||
}
|
||||
|
||||
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
|
||||
self.output.put_u8(COMPOUND_ID);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
|
||||
self.output.put_u8(COMPOUND_ID);
|
||||
|
||||
match &mut self.state {
|
||||
State::Root(root_name) => {
|
||||
if let Some(root_name) = root_name {
|
||||
self.output
|
||||
.put(NbtTag::String(root_name.clone()).serialize_data());
|
||||
}
|
||||
}
|
||||
State::Named(string) => {
|
||||
self.output
|
||||
.put(NbtTag::String(string.clone()).serialize_data());
|
||||
}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn serialize_struct_variant(
|
||||
self,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_len: usize,
|
||||
) -> Result<Self::SerializeStructVariant> {
|
||||
Err(Error::UnsupportedType("struct variant".to_string()))
|
||||
}
|
||||
|
||||
fn is_human_readable(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ser::SerializeSeq for &'a mut Serializer {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(&mut **self)?;
|
||||
self.state = State::ListElement;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ser::SerializeStruct for &'a mut Serializer {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.state = State::Named(key.to_string());
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
self.output.put_u8(END_ID);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ser::SerializeMap for &'a mut Serializer {
|
||||
type Ok = ();
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_key<T>(&mut self, key: &T) -> std::result::Result<(), Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
self.state = State::MapKey;
|
||||
key.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn serialize_value<T>(&mut self, value: &T) -> std::result::Result<(), Self::Error>
|
||||
where
|
||||
T: ?Sized + Serialize,
|
||||
{
|
||||
value.serialize(&mut **self)
|
||||
}
|
||||
|
||||
fn end(self) -> Result<()> {
|
||||
self.output.put_u8(END_ID);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
277
pumpkin-nbt/src/tag.rs
Normal file
277
pumpkin-nbt/src/tag.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use compound::NbtCompound;
|
||||
|
||||
use crate::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
pub enum NbtTag {
|
||||
End = END_ID,
|
||||
Byte(i8) = BYTE_ID,
|
||||
Short(i16) = SHORT_ID,
|
||||
Int(i32) = INT_ID,
|
||||
Long(i64) = LONG_ID,
|
||||
Float(f32) = FLOAT_ID,
|
||||
Double(f64) = DOUBLE_ID,
|
||||
ByteArray(Bytes) = BYTE_ARRAY_ID,
|
||||
String(String) = STRING_ID,
|
||||
List(Vec<NbtTag>) = LIST_ID,
|
||||
Compound(NbtCompound) = COMPOUND_ID,
|
||||
IntArray(Vec<i32>) = INT_ARRAY_ID,
|
||||
LongArray(Vec<i64>) = LONG_ARRAY_ID,
|
||||
}
|
||||
|
||||
impl NbtTag {
|
||||
/// Returns the numeric id associated with the data type.
|
||||
pub const fn get_type_id(&self) -> u8 {
|
||||
// See https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting
|
||||
unsafe { *(self as *const Self as *const u8) }
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Bytes {
|
||||
let mut bytes = BytesMut::new();
|
||||
bytes.put_u8(self.get_type_id());
|
||||
bytes.put(self.serialize_data());
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
pub fn serialize_data(&self) -> Bytes {
|
||||
let mut bytes = BytesMut::new();
|
||||
match self {
|
||||
NbtTag::End => {}
|
||||
NbtTag::Byte(byte) => bytes.put_i8(*byte),
|
||||
NbtTag::Short(short) => bytes.put_i16(*short),
|
||||
NbtTag::Int(int) => bytes.put_i32(*int),
|
||||
NbtTag::Long(long) => bytes.put_i64(*long),
|
||||
NbtTag::Float(float) => bytes.put_f32(*float),
|
||||
NbtTag::Double(double) => bytes.put_f64(*double),
|
||||
NbtTag::ByteArray(byte_array) => {
|
||||
bytes.put_i32(byte_array.len() as i32);
|
||||
bytes.put_slice(byte_array);
|
||||
}
|
||||
NbtTag::String(string) => {
|
||||
let java_string = cesu8::to_java_cesu8(string);
|
||||
bytes.put_u16(java_string.len() as u16);
|
||||
bytes.put_slice(&java_string);
|
||||
}
|
||||
NbtTag::List(list) => {
|
||||
bytes.put_u8(list.first().unwrap_or(&NbtTag::End).get_type_id());
|
||||
bytes.put_i32(list.len() as i32);
|
||||
for nbt_tag in list {
|
||||
bytes.put(nbt_tag.serialize_data())
|
||||
}
|
||||
}
|
||||
NbtTag::Compound(compound) => {
|
||||
bytes.put(compound.serialize_content());
|
||||
}
|
||||
NbtTag::IntArray(int_array) => {
|
||||
bytes.put_i32(int_array.len() as i32);
|
||||
for int in int_array {
|
||||
bytes.put_i32(*int)
|
||||
}
|
||||
}
|
||||
NbtTag::LongArray(long_array) => {
|
||||
bytes.put_i32(long_array.len() as i32);
|
||||
for long in long_array {
|
||||
bytes.put_i64(*long)
|
||||
}
|
||||
}
|
||||
}
|
||||
bytes.freeze()
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &mut impl Buf) -> Result<NbtTag, Error> {
|
||||
let tag_id = bytes.get_u8();
|
||||
Self::deserialize_data(bytes, tag_id)
|
||||
}
|
||||
|
||||
pub fn deserialize_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<NbtTag, Error> {
|
||||
Self::deserialize(cursor)
|
||||
}
|
||||
|
||||
pub fn deserialize_data(bytes: &mut impl Buf, tag_id: u8) -> Result<NbtTag, Error> {
|
||||
match tag_id {
|
||||
END_ID => Ok(NbtTag::End),
|
||||
BYTE_ID => {
|
||||
let byte = bytes.get_i8();
|
||||
Ok(NbtTag::Byte(byte))
|
||||
}
|
||||
SHORT_ID => {
|
||||
let short = bytes.get_i16();
|
||||
Ok(NbtTag::Short(short))
|
||||
}
|
||||
INT_ID => {
|
||||
let int = bytes.get_i32();
|
||||
Ok(NbtTag::Int(int))
|
||||
}
|
||||
LONG_ID => {
|
||||
let long = bytes.get_i64();
|
||||
Ok(NbtTag::Long(long))
|
||||
}
|
||||
FLOAT_ID => {
|
||||
let float = bytes.get_f32();
|
||||
Ok(NbtTag::Float(float))
|
||||
}
|
||||
DOUBLE_ID => {
|
||||
let double = bytes.get_f64();
|
||||
Ok(NbtTag::Double(double))
|
||||
}
|
||||
BYTE_ARRAY_ID => {
|
||||
let len = bytes.get_i32() as usize;
|
||||
let byte_array = bytes.copy_to_bytes(len);
|
||||
Ok(NbtTag::ByteArray(byte_array))
|
||||
}
|
||||
STRING_ID => Ok(NbtTag::String(get_nbt_string(bytes).unwrap())),
|
||||
LIST_ID => {
|
||||
let tag_type_id = bytes.get_u8();
|
||||
let len = bytes.get_i32();
|
||||
let mut list = Vec::with_capacity(len as usize);
|
||||
for _ in 0..len {
|
||||
let tag = NbtTag::deserialize_data(bytes, tag_type_id)?;
|
||||
assert_eq!(tag.get_type_id(), tag_type_id);
|
||||
list.push(tag);
|
||||
}
|
||||
Ok(NbtTag::List(list))
|
||||
}
|
||||
COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::deserialize_content(bytes)?)),
|
||||
INT_ARRAY_ID => {
|
||||
let len = bytes.get_i32() as usize;
|
||||
let mut int_array = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
let int = bytes.get_i32();
|
||||
int_array.push(int);
|
||||
}
|
||||
Ok(NbtTag::IntArray(int_array))
|
||||
}
|
||||
LONG_ARRAY_ID => {
|
||||
let len = bytes.get_i32() as usize;
|
||||
let mut long_array = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
let long = bytes.get_i64();
|
||||
long_array.push(long);
|
||||
}
|
||||
Ok(NbtTag::LongArray(long_array))
|
||||
}
|
||||
_ => Err(Error::UnknownTagId(tag_id)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_data_from_cursor(
|
||||
cursor: &mut Cursor<&[u8]>,
|
||||
tag_id: u8,
|
||||
) -> Result<NbtTag, Error> {
|
||||
Self::deserialize_data(cursor, tag_id)
|
||||
}
|
||||
|
||||
pub fn extract_byte(&self) -> Option<i8> {
|
||||
match self {
|
||||
NbtTag::Byte(byte) => Some(*byte),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_short(&self) -> Option<i16> {
|
||||
match self {
|
||||
NbtTag::Short(short) => Some(*short),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_int(&self) -> Option<i32> {
|
||||
match self {
|
||||
NbtTag::Int(int) => Some(*int),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_long(&self) -> Option<i64> {
|
||||
match self {
|
||||
NbtTag::Long(long) => Some(*long),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_float(&self) -> Option<f32> {
|
||||
match self {
|
||||
NbtTag::Float(float) => Some(*float),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_double(&self) -> Option<f64> {
|
||||
match self {
|
||||
NbtTag::Double(double) => Some(*double),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
NbtTag::Byte(byte) => Some(*byte != 0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_byte_array(&self) -> Option<Bytes> {
|
||||
match self {
|
||||
// Note: Bytes are free to clone, so we can hand out an owned type
|
||||
NbtTag::ByteArray(byte_array) => Some(byte_array.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_string(&self) -> Option<&String> {
|
||||
match self {
|
||||
NbtTag::String(string) => Some(string),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_list(&self) -> Option<&Vec<NbtTag>> {
|
||||
match self {
|
||||
NbtTag::List(list) => Some(list),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_compound(&self) -> Option<&NbtCompound> {
|
||||
match self {
|
||||
NbtTag::Compound(compound) => Some(compound),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_int_array(&self) -> Option<&Vec<i32>> {
|
||||
match self {
|
||||
NbtTag::IntArray(int_array) => Some(int_array),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_long_array(&self) -> Option<&Vec<i64>> {
|
||||
match self {
|
||||
NbtTag::LongArray(long_array) => Some(long_array),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for NbtTag {
|
||||
fn from(value: &str) -> Self {
|
||||
NbtTag::String(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for NbtTag {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
NbtTag::ByteArray(Bytes::copy_from_slice(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for NbtTag {
|
||||
fn from(value: bool) -> Self {
|
||||
NbtTag::Byte(value as i8)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
pumpkin-config = { path = "../pumpkin-config" }
|
||||
pumpkin-macros = { path = "../pumpkin-macros" }
|
||||
pumpkin-world = { path = "../pumpkin-world" }
|
||||
@@ -25,5 +26,3 @@ flate2 = "1.0"
|
||||
# encryption
|
||||
aes = "0.8.4"
|
||||
cfb8 = "0.8.1"
|
||||
|
||||
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use bytes::BytesMut;
|
||||
use pumpkin_macros::client_packet;
|
||||
|
||||
use crate::{bytebuf::ByteBuffer, ClientPacket};
|
||||
@@ -19,7 +20,7 @@ impl<'a> CRegistryData<'a> {
|
||||
|
||||
pub struct RegistryEntry<'a> {
|
||||
pub entry_id: &'a str,
|
||||
pub data: Vec<u8>,
|
||||
pub data: BytesMut,
|
||||
}
|
||||
|
||||
impl<'a> ClientPacket for CRegistryData<'a> {
|
||||
|
||||
@@ -15,8 +15,7 @@ impl<'a> ClientPacket for CChunkData<'a> {
|
||||
buf.put_i32(self.0.position.z);
|
||||
|
||||
let heightmap_nbt =
|
||||
fastnbt::to_bytes_with_opts(&self.0.blocks.heightmap, fastnbt::SerOpts::network_nbt())
|
||||
.unwrap();
|
||||
pumpkin_nbt::serializer::to_bytes_unnamed(&self.0.blocks.heightmap).unwrap();
|
||||
// Heightmaps
|
||||
buf.put_slice(&heightmap_nbt);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ impl<'a> ClientPacket for CUpdateObjectives<'a> {
|
||||
NumberFormat::Styled(style) => {
|
||||
p.put_var_int(&VarInt(1));
|
||||
// TODO
|
||||
p.put_slice(&fastnbt::to_bytes(style).unwrap());
|
||||
p.put_slice(&pumpkin_nbt::serializer::to_bytes_unnamed(style).unwrap());
|
||||
}
|
||||
NumberFormat::Fixed(text_component) => {
|
||||
p.put_var_int(&VarInt(2));
|
||||
|
||||
@@ -5,6 +5,7 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pumpkin-protocol = { path = "../pumpkin-protocol" }
|
||||
pumpkin-nbt = { path = "../pumpkin-nbt" }
|
||||
pumpkin-core = { path = "../pumpkin-core" }
|
||||
|
||||
serde.workspace = true
|
||||
@@ -15,7 +16,4 @@ rayon.workspace = true
|
||||
num-traits.workspace = true
|
||||
num-derive.workspace = true
|
||||
|
||||
# nbt
|
||||
fastnbt = { git = "https://github.com/owengage/fastnbt.git" }
|
||||
|
||||
itertools.workspace = true
|
||||
|
||||
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Biome {
|
||||
has_precipitation: i8,
|
||||
has_precipitation: bool,
|
||||
temperature: f32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
temperature_modifier: Option<String>,
|
||||
@@ -67,5 +67,5 @@ struct Music {
|
||||
sound: String,
|
||||
min_delay: i32,
|
||||
max_delay: i32,
|
||||
replace_current_music: i8,
|
||||
replace_current_music: bool,
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Dimension {
|
||||
ambient_light: f32,
|
||||
bed_works: u8,
|
||||
bed_works: bool,
|
||||
coordinate_scale: f64,
|
||||
effects: DimensionEffects,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fixed_time: Option<i64>,
|
||||
has_ceiling: u8,
|
||||
has_raids: u8,
|
||||
has_skylight: u8,
|
||||
has_ceiling: bool,
|
||||
has_raids: bool,
|
||||
has_skylight: bool,
|
||||
height: i32,
|
||||
infiniburn: String,
|
||||
logical_height: i32,
|
||||
min_y: i32,
|
||||
monster_spawn_block_light_limit: i32,
|
||||
monster_spawn_light_level: MonsterSpawnLightLevel,
|
||||
natural: u8,
|
||||
piglin_safe: u8,
|
||||
respawn_anchor_works: u8,
|
||||
ultrawarm: u8,
|
||||
natural: bool,
|
||||
piglin_safe: bool,
|
||||
respawn_anchor_works: bool,
|
||||
ultrawarm: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Default, Debug)]
|
||||
|
||||
@@ -6,7 +6,6 @@ use chat_type::ChatType;
|
||||
use damage_type::DamageType;
|
||||
use dimension::Dimension;
|
||||
use enchantment::Enchantment;
|
||||
use fastnbt::SerOpts;
|
||||
use instrument::Instrument;
|
||||
use jukebox_song::JukeboxSong;
|
||||
use paint::Painting;
|
||||
@@ -80,7 +79,7 @@ impl Registry {
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let biome = Registry {
|
||||
@@ -93,7 +92,7 @@ impl Registry {
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let chat_type = Registry {
|
||||
@@ -106,7 +105,7 @@ impl Registry {
|
||||
// .iter()
|
||||
// .map(|s| RegistryEntry {
|
||||
// entry_id: s.0,
|
||||
// data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
// })
|
||||
// .collect();
|
||||
// let trim_pattern = Registry {
|
||||
@@ -119,7 +118,7 @@ impl Registry {
|
||||
// .iter()
|
||||
// .map(|s| RegistryEntry {
|
||||
// entry_id: s.0,
|
||||
// data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
// })
|
||||
// .collect();
|
||||
// let trim_material = Registry {
|
||||
@@ -135,7 +134,7 @@ impl Registry {
|
||||
let varient = s.1.clone();
|
||||
RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&varient, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&varient).unwrap(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -149,7 +148,7 @@ impl Registry {
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let painting_variant = Registry {
|
||||
@@ -162,7 +161,7 @@ impl Registry {
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let dimension_type = Registry {
|
||||
@@ -175,7 +174,7 @@ impl Registry {
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let damage_type = Registry {
|
||||
@@ -188,7 +187,7 @@ impl Registry {
|
||||
.iter()
|
||||
.map(|s| RegistryEntry {
|
||||
entry_id: s.0,
|
||||
data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
})
|
||||
.collect();
|
||||
let banner_pattern = Registry {
|
||||
@@ -202,7 +201,7 @@ impl Registry {
|
||||
// .iter()
|
||||
// .map(|s| RegistryEntry {
|
||||
// entry_id: s.0,
|
||||
// data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
// })
|
||||
// .collect();
|
||||
// let enchantment = Registry {
|
||||
@@ -215,7 +214,7 @@ impl Registry {
|
||||
// .iter()
|
||||
// .map(|s| RegistryEntry {
|
||||
// entry_id: s.0,
|
||||
// data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
// })
|
||||
// .collect();
|
||||
// let jukebox_song = Registry {
|
||||
@@ -228,7 +227,7 @@ impl Registry {
|
||||
// .iter()
|
||||
// .map(|s| RegistryEntry {
|
||||
// entry_id: s.0,
|
||||
// data: fastnbt::to_bytes_with_opts(&s.1, SerOpts::network_nbt()).unwrap(),
|
||||
// data: pumpkin_nbt::serializer::to_bytes_unnamed(&s.1).unwrap(),
|
||||
// })
|
||||
// .collect();
|
||||
// let instrument = Registry {
|
||||
|
||||
@@ -5,5 +5,5 @@ pub struct TrimPattern {
|
||||
asset_id: String,
|
||||
template_item: String,
|
||||
// description: TextComponent<'static>,
|
||||
decal: u8,
|
||||
decal: bool,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Index;
|
||||
|
||||
use fastnbt::LongArray;
|
||||
use pumpkin_core::math::vector2::Vector2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Index;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
@@ -78,6 +77,7 @@ struct PaletteEntry {
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct ChunkSectionBlockStates {
|
||||
// #[serde(with = "LongArray")]
|
||||
data: Option<LongArray>,
|
||||
palette: Vec<PaletteEntry>,
|
||||
}
|
||||
@@ -85,7 +85,9 @@ struct ChunkSectionBlockStates {
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct ChunkHeightmaps {
|
||||
// #[serde(with = "LongArray")]
|
||||
motion_blocking: LongArray,
|
||||
// #[serde(with = "LongArray")]
|
||||
world_surface: LongArray,
|
||||
}
|
||||
|
||||
@@ -269,8 +271,7 @@ impl ChunkData {
|
||||
continue;
|
||||
}
|
||||
Some(d) => d,
|
||||
}
|
||||
.into_inner();
|
||||
};
|
||||
|
||||
// How many bits each block has in one of the pallete u64s
|
||||
let block_bit_size = {
|
||||
|
||||
Reference in New Issue
Block a user