Pumpkin/crates/pumpkin-protocol/src/ser/mod.rs
2026-08-28 22:32:50 +02:00

1265 lines
43 KiB
Rust

use core::str;
use std::borrow::Cow;
use std::io::{Read, Write};
use crate::{
FixedBitSet,
codec::{
bit_set::BitSet, var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong,
},
};
use pumpkin_nbt::{
compound::NbtCompound, deserializer::NbtReadHelper, serializer::NbtWriteHelperJava, tag::NbtTag,
};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::{text::TextComponent, version::JavaMinecraftVersion};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReadingError {
#[error("EOF, Tried to read {0} but No bytes left to consume")]
CleanEOF(String),
#[error("incomplete: {0}")]
Incomplete(String),
#[error("too large: {0}")]
TooLarge(String),
#[error("{0}")]
Message(String),
}
impl serde::de::Error for ReadingError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self::Message(msg.to_string())
}
}
#[derive(Debug, Error)]
pub enum WritingError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Serde failure: {0}")]
Serde(String),
#[error("Packet is not supported in Minecraft version {0:?}")]
UnsupportedVersion(JavaMinecraftVersion),
#[error("Failed to serialize packet: {0}")]
Message(String),
}
impl serde::ser::Error for WritingError {
fn custom<T: std::fmt::Display>(msg: T) -> Self {
Self::Serde(msg.to_string())
}
}
struct NetworkReadDataSource<'a, R: NetworkReadExt + ?Sized>(&'a mut R);
impl<'a, R: NetworkReadExt + ?Sized> pumpkin_nbt::deserializer::NbtDataSource<'a>
for NetworkReadDataSource<'a, R>
{
fn read_u8(&mut self) -> Result<u8, pumpkin_nbt::Error> {
self.0.get_u8().map_err(|e| {
pumpkin_nbt::Error::Incomplete(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
e.to_string(),
))
})
}
fn read_bytes(&mut self, buf: &mut [u8]) -> Result<(), pumpkin_nbt::Error> {
self.0.read_bytes_to_buf(buf).map_err(|e| {
pumpkin_nbt::Error::Incomplete(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
e.to_string(),
))
})
}
fn seek_relative(&mut self, offset: i64) -> Result<(), pumpkin_nbt::Error> {
if offset < 0 {
return Err(pumpkin_nbt::Error::Incomplete(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"negative seek not supported",
)));
}
let mut skipped = vec![0u8; offset as usize];
self.0.read_bytes_to_buf(&mut skipped).map_err(|e| {
pumpkin_nbt::Error::Incomplete(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
e.to_string(),
))
})
}
fn read_string(&mut self, len: usize) -> Result<Cow<'a, str>, pumpkin_nbt::Error> {
let mut buf = vec![0u8; len];
self.0.read_bytes_to_buf(&mut buf).map_err(|e| {
pumpkin_nbt::Error::Incomplete(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
e.to_string(),
))
})?;
let string =
cesu8::from_java_cesu8(&buf).map_err(|_| pumpkin_nbt::Error::Cesu8DecodingError)?;
Ok(Cow::Owned(string.into_owned()))
}
fn read_byte_array(&mut self, len: usize) -> Result<Cow<'a, [i8]>, pumpkin_nbt::Error> {
let mut buf = vec![0u8; len];
self.0.read_bytes_to_buf(&mut buf).map_err(|e| {
pumpkin_nbt::Error::Incomplete(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
e.to_string(),
))
})?;
let i8_buf: Vec<i8> = buf.into_iter().map(|b| b as i8).collect();
Ok(Cow::Owned(i8_buf))
}
}
pub trait NetworkReadExt {
fn get_i8(&mut self) -> Result<i8, ReadingError>;
fn get_u8(&mut self) -> Result<u8, ReadingError>;
fn get_i16_be(&mut self) -> Result<i16, ReadingError>;
fn get_u16_be(&mut self) -> Result<u16, ReadingError>;
fn get_i32_be(&mut self) -> Result<i32, ReadingError>;
fn get_u32_be(&mut self) -> Result<u32, ReadingError>;
fn get_i64_be(&mut self) -> Result<i64, ReadingError>;
fn get_u64_be(&mut self) -> Result<u64, ReadingError>;
fn get_f32_be(&mut self) -> Result<f32, ReadingError>;
fn get_f64_be(&mut self) -> Result<f64, ReadingError>;
fn get_i128_be(&mut self) -> Result<i128, ReadingError>;
fn get_u128_be(&mut self) -> Result<u128, ReadingError>;
#[inline]
fn get_i16(&mut self) -> Result<i16, ReadingError> {
self.get_i16_be()
}
#[inline]
fn get_u16(&mut self) -> Result<u16, ReadingError> {
self.get_u16_be()
}
#[inline]
fn get_i32(&mut self) -> Result<i32, ReadingError> {
self.get_i32_be()
}
#[inline]
fn get_u32(&mut self) -> Result<u32, ReadingError> {
self.get_u32_be()
}
#[inline]
fn get_i64(&mut self) -> Result<i64, ReadingError> {
self.get_i64_be()
}
#[inline]
fn get_u64(&mut self) -> Result<u64, ReadingError> {
self.get_u64_be()
}
#[inline]
fn get_f32(&mut self) -> Result<f32, ReadingError> {
self.get_f32_be()
}
#[inline]
fn get_f64(&mut self) -> Result<f64, ReadingError> {
self.get_f64_be()
}
fn read_bytes_to_buf(&mut self, buf: &mut [u8]) -> Result<(), ReadingError>;
fn get_bool(&mut self) -> Result<bool, ReadingError>;
fn get_var_int(&mut self) -> Result<VarInt, ReadingError>;
fn get_var_uint(&mut self) -> Result<VarUInt, ReadingError>;
fn get_var_long(&mut self) -> Result<VarLong, ReadingError>;
fn get_var_ulong(&mut self) -> Result<VarULong, ReadingError>;
fn get_str_bounded(&mut self, bound: usize) -> Result<Box<str>, ReadingError>;
#[inline]
fn get_str(&mut self) -> Result<Box<str>, ReadingError> {
self.get_str_bounded(32767)
}
fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError>;
fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, ReadingError>;
#[inline]
fn get_block_pos(&mut self, version: &JavaMinecraftVersion) -> Result<BlockPos, ReadingError> {
let val = self.get_i64_be()?;
Ok(BlockPos::from_long_for_version(val, version))
}
#[inline]
fn get_container_id(&mut self, version: &JavaMinecraftVersion) -> Result<VarInt, ReadingError> {
if *version >= JavaMinecraftVersion::V_1_21_2 {
self.get_var_int()
} else {
Ok(VarInt(i32::from(self.get_u8()?)))
}
}
#[inline]
fn get_option<G>(
&mut self,
parse: impl FnOnce(&mut Self) -> Result<G, ReadingError>,
) -> Result<Option<G>, ReadingError> {
if self.get_bool()? {
Ok(Some(parse(self)?))
} else {
Ok(None)
}
}
#[inline]
fn get_list<G>(
&mut self,
parse: impl Fn(&mut Self) -> Result<G, ReadingError>,
) -> Result<Vec<G>, ReadingError> {
const MAX_LIST_SIZE: usize = 65536;
let len = self.get_var_int()?.0 as usize;
if len > MAX_LIST_SIZE {
return Err(ReadingError::TooLarge(format!(
"List length {len} exceeds limit"
)));
}
let mut list = Vec::with_capacity(len);
for _ in 0..len {
list.push(parse(self)?);
}
Ok(list)
}
fn get_nbt_with_version(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtTag>, ReadingError> {
if *version >= JavaMinecraftVersion::V_1_8 {
let tag_id = self.get_u8()?;
if tag_id == pumpkin_nbt::END_ID {
return Ok(None);
}
let mut helper =
pumpkin_nbt::deserializer::NbtReadHelperJava::new(NetworkReadDataSource(self));
if *version < JavaMinecraftVersion::V_1_20_2 {
let _name = helper
.get_string()
.map_err(|e| ReadingError::Message(e.to_string()))?;
}
let tag = if tag_id == pumpkin_nbt::COMPOUND_ID {
NbtTag::Compound(
NbtCompound::deserialize_content(&mut helper)
.map_err(|e| ReadingError::Message(e.to_string()))?,
)
} else {
NbtTag::deserialize_data(&mut helper, tag_id)
.map_err(|e| ReadingError::Message(e.to_string()))?
};
Ok(Some(tag))
} else {
let length = self.get_i16_be()?;
if length <= 0 {
return Ok(None);
}
let mut compressed = vec![0u8; length as usize];
self.read_bytes_to_buf(&mut compressed)?;
let mut decoder = flate2::read::GzDecoder::new(&compressed[..]);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| ReadingError::Message(e.to_string()))?;
let mut cursor = std::io::Cursor::new(decompressed);
let mut helper = pumpkin_nbt::deserializer::NbtReadHelperJava::new(&mut cursor);
let tag_id = helper
.get_u8()
.map_err(|e| ReadingError::Message(e.to_string()))?;
if tag_id == pumpkin_nbt::END_ID {
return Ok(None);
}
let _name = helper
.get_string()
.map_err(|e| ReadingError::Message(e.to_string()))?;
let tag = if tag_id == pumpkin_nbt::COMPOUND_ID {
NbtTag::Compound(
NbtCompound::deserialize_content(&mut helper)
.map_err(|e| ReadingError::Message(e.to_string()))?,
)
} else {
NbtTag::deserialize_data(&mut helper, tag_id)
.map_err(|e| ReadingError::Message(e.to_string()))?
};
Ok(Some(tag))
}
}
#[inline]
fn get_compound_nbt_with_version(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtCompound>, ReadingError> {
match self.get_nbt_with_version(version)? {
Some(NbtTag::Compound(comp)) => Ok(Some(comp)),
Some(NbtTag::End) | None => Ok(None),
Some(other) => Err(ReadingError::Message(format!(
"Expected NBT compound, found tag ID {}",
other.get_type_id()
))),
}
}
}
pub trait NetworkReadSliceExt<'a> {
fn get_component_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<TextComponent, ReadingError>;
#[inline]
fn get_component(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<TextComponent, ReadingError> {
self.get_component_borrowed(version)
}
fn get_str_borrowed(&mut self) -> Result<&'a str, ReadingError>;
fn get_str_bounded_borrowed(&mut self, bound: usize) -> Result<&'a str, ReadingError>;
fn read_slice_borrowed(&mut self, count: usize) -> Result<&'a [u8], ReadingError>;
fn read_remaining_slice_borrowed(&mut self, bound: usize) -> Result<&'a [u8], ReadingError>;
fn get_nbt_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtTag>, ReadingError>;
#[inline]
fn get_nbt(&mut self, version: &JavaMinecraftVersion) -> Result<Option<NbtTag>, ReadingError> {
self.get_nbt_borrowed(version)
}
#[inline]
fn get_compound_nbt_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtCompound>, ReadingError> {
match self.get_nbt_borrowed(version)? {
Some(NbtTag::Compound(comp)) => Ok(Some(comp)),
Some(NbtTag::End) | None => Ok(None),
Some(other) => Err(ReadingError::Message(format!(
"Expected NBT compound, found tag ID {}",
other.get_type_id()
))),
}
}
#[inline]
fn get_compound_nbt(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtCompound>, ReadingError> {
self.get_compound_nbt_borrowed(version)
}
#[inline]
fn read_cow_slice_borrowed(&mut self, count: usize) -> Result<Cow<'a, [u8]>, ReadingError> {
Ok(Cow::Borrowed(self.read_slice_borrowed(count)?))
}
#[inline]
fn read_remaining_to_cow_slice_borrowed(
&mut self,
bound: usize,
) -> Result<Cow<'a, [u8]>, ReadingError> {
Ok(Cow::Borrowed(self.read_remaining_slice_borrowed(bound)?))
}
#[inline]
fn get_cow_str_borrowed(&mut self) -> Result<Cow<'a, str>, ReadingError> {
self.get_cow_str_bounded_borrowed(32767)
}
#[inline]
fn get_cow_str_bounded_borrowed(&mut self, bound: usize) -> Result<Cow<'a, str>, ReadingError> {
Ok(Cow::Borrowed(self.get_str_bounded_borrowed(bound)?))
}
}
impl<'a> NetworkReadSliceExt<'a> for &'a [u8] {
#[inline]
fn read_slice_borrowed(&mut self, count: usize) -> Result<&'a [u8], ReadingError> {
if self.len() < count {
return Err(ReadingError::Incomplete(format!(
"EOF, Tried to read {count} bytes but only {} bytes left",
self.len()
)));
}
let (head, tail) = self.split_at(count);
*self = tail;
Ok(head)
}
#[inline]
fn read_remaining_slice_borrowed(&mut self, bound: usize) -> Result<&'a [u8], ReadingError> {
if self.len() > bound {
return Err(ReadingError::TooLarge(
"Read remaining too long".to_string(),
));
}
let slice = *self;
*self = &[];
Ok(slice)
}
#[inline]
fn get_str_bounded_borrowed(&mut self, bound: usize) -> Result<&'a str, ReadingError> {
let bytes_len = self.get_var_uint()?.0 as usize;
let maximum_utf8_bytes = bound.saturating_mul(3).min(crate::MAX_PACKET_DATA_SIZE);
if bytes_len > maximum_utf8_bytes {
return Err(ReadingError::TooLarge(format!(
"string has too many bytes ({bytes_len} > {maximum_utf8_bytes})"
)));
}
let bytes = self.read_slice_borrowed(bytes_len)?;
let string =
std::str::from_utf8(bytes).map_err(|e| ReadingError::Message(e.to_string()))?;
if string.encode_utf16().nth(bound).is_some() {
return Err(ReadingError::TooLarge(format!(
"string has too many UTF-16 characters (more than the maximum limit {bound})"
)));
}
Ok(string)
}
#[inline]
fn get_str_borrowed(&mut self) -> Result<&'a str, ReadingError> {
self.get_str_bounded_borrowed(32767)
}
#[inline]
fn get_component_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<TextComponent, ReadingError> {
if *version < JavaMinecraftVersion::V_1_20_3 {
let max_len = if *version >= JavaMinecraftVersion::V_1_13 {
262144
} else {
32767
};
let json = self.get_str_bounded_borrowed(max_len)?;
serde_json::from_str(json)
.map_err(|e| ReadingError::Message(format!("Invalid component JSON: {e}")))
} else {
let mut cursor = std::io::Cursor::new(*self);
let mut nbt_reader = pumpkin_nbt::deserializer::NbtReadHelperJava::new(&mut cursor);
let nbt = NbtTag::deserialize(&mut nbt_reader)
.map_err(|e| ReadingError::Message(format!("Invalid component NBT: {e}")))?;
let bytes_read = cursor.position() as usize;
*self = &self[bytes_read..];
let json_value = nbt_tag_to_json(&nbt);
serde_json::from_value(json_value).map_err(|e| {
ReadingError::Message(format!("Failed to parse component from NBT: {e}"))
})
}
}
fn get_nbt_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtTag>, ReadingError> {
if *version >= JavaMinecraftVersion::V_1_8 {
if self.is_empty() {
return Ok(None);
}
if (*self)[0] == pumpkin_nbt::END_ID {
*self = &(*self)[1..];
return Ok(None);
}
let mut cursor = std::io::Cursor::new(*self);
let mut helper = pumpkin_nbt::deserializer::NbtReadHelperJava::new(&mut cursor);
let tag_id = helper
.get_u8()
.map_err(|e| ReadingError::Message(e.to_string()))?;
if tag_id == pumpkin_nbt::END_ID {
let pos = cursor.position() as usize;
*self = &(*self)[pos..];
return Ok(None);
}
if *version < JavaMinecraftVersion::V_1_20_2 {
let _name = helper
.get_string()
.map_err(|e| ReadingError::Message(e.to_string()))?;
}
let tag = if tag_id == pumpkin_nbt::COMPOUND_ID {
NbtTag::Compound(
NbtCompound::deserialize_content(&mut helper)
.map_err(|e| ReadingError::Message(e.to_string()))?,
)
} else {
NbtTag::deserialize_data(&mut helper, tag_id)
.map_err(|e| ReadingError::Message(e.to_string()))?
};
let pos = cursor.position() as usize;
*self = &(*self)[pos..];
Ok(Some(tag))
} else {
let length = self.get_i16_be()?;
if length <= 0 {
return Ok(None);
}
let length = length as usize;
if self.len() < length {
return Err(ReadingError::Incomplete(
"Not enough bytes for compressed NBT".into(),
));
}
let compressed = &(*self)[..length];
*self = &(*self)[length..];
let mut decoder = flate2::read::GzDecoder::new(compressed);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| ReadingError::Message(e.to_string()))?;
let mut cursor = std::io::Cursor::new(decompressed);
let mut helper = pumpkin_nbt::deserializer::NbtReadHelperJava::new(&mut cursor);
let tag_id = helper
.get_u8()
.map_err(|e| ReadingError::Message(e.to_string()))?;
if tag_id == pumpkin_nbt::END_ID {
return Ok(None);
}
let _name = helper
.get_string()
.map_err(|e| ReadingError::Message(e.to_string()))?;
let tag = if tag_id == pumpkin_nbt::COMPOUND_ID {
NbtTag::Compound(
NbtCompound::deserialize_content(&mut helper)
.map_err(|e| ReadingError::Message(e.to_string()))?,
)
} else {
NbtTag::deserialize_data(&mut helper, tag_id)
.map_err(|e| ReadingError::Message(e.to_string()))?
};
Ok(Some(tag))
}
}
}
impl<'a, R: NetworkReadSliceExt<'a> + ?Sized> NetworkReadSliceExt<'a> for &mut R {
#[inline]
fn get_component_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<TextComponent, ReadingError> {
(**self).get_component_borrowed(version)
}
#[inline]
fn get_str_borrowed(&mut self) -> Result<&'a str, ReadingError> {
(**self).get_str_borrowed()
}
#[inline]
fn get_str_bounded_borrowed(&mut self, bound: usize) -> Result<&'a str, ReadingError> {
(**self).get_str_bounded_borrowed(bound)
}
#[inline]
fn read_slice_borrowed(&mut self, count: usize) -> Result<&'a [u8], ReadingError> {
(**self).read_slice_borrowed(count)
}
#[inline]
fn read_remaining_slice_borrowed(&mut self, bound: usize) -> Result<&'a [u8], ReadingError> {
(**self).read_remaining_slice_borrowed(bound)
}
#[inline]
fn get_nbt_borrowed(
&mut self,
version: &JavaMinecraftVersion,
) -> Result<Option<NbtTag>, ReadingError> {
(**self).get_nbt_borrowed(version)
}
}
macro_rules! get_number_be {
($name:ident, $type:ty) => {
#[inline]
fn $name(&mut self) -> Result<$type, ReadingError> {
let mut buf = [0u8; std::mem::size_of::<$type>()];
self.read_exact(&mut buf)
.map_err(|err| ReadingError::Incomplete(err.to_string().into()))?;
Ok(<$type>::from_be_bytes(buf))
}
};
}
impl<R: Read> NetworkReadExt for R {
get_number_be!(get_u8, u8);
get_number_be!(get_i8, i8);
get_number_be!(get_i16_be, i16);
get_number_be!(get_u16_be, u16);
get_number_be!(get_i32_be, i32);
get_number_be!(get_u32_be, u32);
get_number_be!(get_i64_be, i64);
get_number_be!(get_u64_be, u64);
get_number_be!(get_i128_be, i128);
get_number_be!(get_u128_be, u128);
get_number_be!(get_f32_be, f32);
get_number_be!(get_f64_be, f64);
#[inline]
fn read_bytes_to_buf(&mut self, buf: &mut [u8]) -> Result<(), ReadingError> {
self.read_exact(buf)
.map_err(|err| ReadingError::Incomplete(err.to_string()))
}
#[inline]
fn get_bool(&mut self) -> Result<bool, ReadingError> {
let byte = self.get_u8()?;
Ok(byte != 0)
}
#[inline]
fn get_var_int(&mut self) -> Result<VarInt, ReadingError> {
VarInt::decode(self)
}
#[inline]
fn get_var_uint(&mut self) -> Result<VarUInt, ReadingError> {
VarUInt::decode(self)
}
#[inline]
fn get_var_long(&mut self) -> Result<VarLong, ReadingError> {
VarLong::decode(self)
}
#[inline]
fn get_var_ulong(&mut self) -> Result<VarULong, ReadingError> {
VarULong::decode(self)
}
#[inline]
fn get_str_bounded(&mut self, bound: usize) -> Result<Box<str>, ReadingError> {
let bytes_len = self.get_var_uint()?.0 as usize;
// We treat `bound` as the maximum number of Java `char`s allowed.
// First, check if there are too many bytes to even fit in the UTF-16 bound.
// 1 Java `char` takes a maximum of 3 bytes in UTF-8:
let maximum_utf8_bytes = bound.saturating_mul(3).min(crate::MAX_PACKET_DATA_SIZE);
if bytes_len > maximum_utf8_bytes {
return Err(ReadingError::TooLarge(format!(
"string has too many bytes ({bytes_len} > {maximum_utf8_bytes})"
)));
}
if bytes_len <= 128 {
let mut stack_buf = [0u8; 128];
let slice = &mut stack_buf[..bytes_len];
self.read_exact(slice)
.map_err(|err| ReadingError::Incomplete(err.to_string()))?;
let string =
std::str::from_utf8(slice).map_err(|e| ReadingError::Message(e.to_string()))?;
if string.encode_utf16().nth(bound).is_some() {
return Err(ReadingError::TooLarge(format!(
"string has too many UTF-16 characters (more than the maximum limit {bound})"
)));
}
Ok(string.into())
} else {
let mut data = vec![0u8; bytes_len];
self.read_bytes_to_buf(&mut data)?;
let string =
std::str::from_utf8(&data).map_err(|e| ReadingError::Message(e.to_string()))?;
if string.encode_utf16().nth(bound).is_some() {
return Err(ReadingError::TooLarge(format!(
"string has too many UTF-16 characters (more than the maximum limit {bound})"
)));
}
Ok(string.into())
}
}
#[inline]
fn get_uuid(&mut self) -> Result<uuid::Uuid, ReadingError> {
let mut bytes = [0u8; 16];
self.read_exact(&mut bytes)
.map_err(|err| ReadingError::Incomplete(err.to_string()))?;
Ok(uuid::Uuid::from_bytes(bytes))
}
#[inline]
fn get_fixed_bitset(&mut self, bits: usize) -> Result<FixedBitSet, ReadingError> {
let byte_count = bits.div_ceil(8);
let mut bytes = vec![0u8; byte_count];
self.read_bytes_to_buf(&mut bytes)?;
Ok(bytes.into_boxed_slice())
}
}
#[must_use]
pub fn json_to_nbt_tag(value: &serde_json::Value) -> NbtTag {
match value {
serde_json::Value::Null => NbtTag::Compound(NbtCompound::new()),
serde_json::Value::Bool(b) => NbtTag::Byte(i8::from(*b)),
serde_json::Value::Number(num) => num.as_i64().map_or_else(
|| num.as_f64().map_or(NbtTag::Int(0), NbtTag::Double),
|i| {
i8::try_from(i).map_or_else(
|_| {
i16::try_from(i).map_or_else(
|_| i32::try_from(i).map_or(NbtTag::Long(i), NbtTag::Int),
NbtTag::Short,
)
},
NbtTag::Byte,
)
},
),
serde_json::Value::String(s) => NbtTag::String(s.clone().into_boxed_str()),
serde_json::Value::Array(arr) => {
let list: Vec<NbtTag> = arr.iter().map(json_to_nbt_tag).collect();
NbtTag::List(list)
}
serde_json::Value::Object(obj) => {
let mut compound = NbtCompound::new();
for (k, v) in obj {
compound.put(k, json_to_nbt_tag(v));
}
NbtTag::Compound(compound)
}
}
}
#[must_use]
pub fn nbt_tag_to_json(tag: &NbtTag) -> serde_json::Value {
nbt_tag_to_json_ext(tag, false)
}
#[must_use]
pub fn nbt_tag_to_json_ext(tag: &NbtTag, parse_byte_as_bool: bool) -> serde_json::Value {
match tag {
NbtTag::End => serde_json::Value::Null,
NbtTag::Byte(b) => {
if parse_byte_as_bool {
if *b == 0 {
serde_json::Value::Bool(false)
} else if *b == 1 {
serde_json::Value::Bool(true)
} else {
serde_json::Value::Number((*b).into())
}
} else {
serde_json::Value::Number((*b).into())
}
}
NbtTag::Short(s) => serde_json::Value::Number((*s).into()),
NbtTag::Int(i) => serde_json::Value::Number((*i).into()),
NbtTag::Long(l) => serde_json::Value::Number((*l).into()),
NbtTag::Float(f) => serde_json::Number::from_f64(f64::from(*f))
.map_or(serde_json::Value::Null, serde_json::Value::Number),
NbtTag::Double(d) => serde_json::Number::from_f64(*d)
.map_or(serde_json::Value::Null, serde_json::Value::Number),
NbtTag::ByteArray(bytes) => serde_json::Value::Array(
bytes
.iter()
.map(|b| serde_json::Value::Number((*b).into()))
.collect(),
),
NbtTag::String(s) => serde_json::Value::String(s.to_string()),
NbtTag::List(list) => serde_json::Value::Array(
list.iter()
.map(|t| nbt_tag_to_json_ext(t, parse_byte_as_bool))
.collect(),
),
NbtTag::Compound(compound) => {
let mut map = serde_json::Map::new();
for (k, v) in &compound.child_tags {
map.insert(k.to_string(), nbt_tag_to_json_ext(v, parse_byte_as_bool));
}
serde_json::Value::Object(map)
}
NbtTag::IntArray(ints) => serde_json::Value::Array(
ints.iter()
.map(|i| serde_json::Value::Number((*i).into()))
.collect(),
),
NbtTag::LongArray(longs) => serde_json::Value::Array(
longs
.iter()
.map(|l| serde_json::Value::Number((*l).into()))
.collect(),
),
}
}
pub fn write_nbt_payload(
mut write: impl Write,
nbt_data: &[u8],
version: &JavaMinecraftVersion,
) -> Result<(), WritingError> {
if *version >= JavaMinecraftVersion::V_1_8 {
if nbt_data.is_empty() || nbt_data == [0] {
write.write_u8(0)?;
} else if *version < JavaMinecraftVersion::V_1_20_2 {
if nbt_data.len() >= 3 && nbt_data[0] == 0x0A && nbt_data[1] == 0 && nbt_data[2] == 0 {
write.write_all(nbt_data).map_err(WritingError::IoError)?;
} else if nbt_data[0] == 0x0A {
write.write_u8(0x0A)?;
write.write_u16_be(0)?;
write
.write_all(&nbt_data[1..])
.map_err(WritingError::IoError)?;
} else {
write.write_all(nbt_data).map_err(WritingError::IoError)?;
}
} else {
write.write_all(nbt_data).map_err(WritingError::IoError)?;
}
} else {
if nbt_data.is_empty() || nbt_data == [0] {
write.write_i16_be(-1)?;
} else {
let mut named_bytes = Vec::with_capacity(nbt_data.len() + 2);
if nbt_data.len() >= 3 && nbt_data[0] == 0x0A && nbt_data[1] == 0 && nbt_data[2] == 0 {
named_bytes.extend_from_slice(nbt_data);
} else if nbt_data[0] == 0x0A {
named_bytes.push(0x0A);
named_bytes.extend_from_slice(&[0x00, 0x00]);
named_bytes.extend_from_slice(&nbt_data[1..]);
} else {
named_bytes.extend_from_slice(nbt_data);
}
let mut encoder =
flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
encoder
.write_all(&named_bytes)
.map_err(WritingError::IoError)?;
let compressed = encoder.finish().map_err(WritingError::IoError)?;
write.write_i16_be(compressed.len() as i16)?;
write
.write_all(&compressed)
.map_err(WritingError::IoError)?;
}
}
Ok(())
}
pub fn read_nbt_payload(
bytebuf: &mut &[u8],
version: &JavaMinecraftVersion,
) -> Result<Box<[u8]>, ReadingError> {
if *version >= JavaMinecraftVersion::V_1_8 {
if bytebuf.is_empty() || bytebuf[0] == 0 {
if !bytebuf.is_empty() {
let _ = bytebuf.get_u8()?;
}
Ok(Box::new([]))
} else if *version < JavaMinecraftVersion::V_1_20_2 {
let all = bytebuf.to_vec();
*bytebuf = &[];
if all.len() >= 3 && all[0] == 0x0A && all[1] == 0 && all[2] == 0 {
let mut unnamed = Vec::with_capacity(all.len() - 2);
unnamed.push(0x0A);
unnamed.extend_from_slice(&all[3..]);
Ok(unnamed.into_boxed_slice())
} else {
Ok(all.into_boxed_slice())
}
} else {
let all = bytebuf.to_vec().into_boxed_slice();
*bytebuf = &[];
Ok(all)
}
} else {
let length = bytebuf.get_i16_be()?;
if length <= 0 {
Ok(Box::new([]))
} else {
if bytebuf.len() < length as usize {
return Err(ReadingError::Incomplete(
"Not enough bytes for compressed NBT".into(),
));
}
let compressed = &bytebuf[..length as usize];
*bytebuf = &bytebuf[length as usize..];
let mut decoder = flate2::read::GzDecoder::new(compressed);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.map_err(|e| ReadingError::Message(e.to_string()))?;
if decompressed.len() >= 3
&& decompressed[0] == 0x0A
&& decompressed[1] == 0
&& decompressed[2] == 0
{
let mut unnamed = Vec::with_capacity(decompressed.len() - 2);
unnamed.push(0x0A);
unnamed.extend_from_slice(&decompressed[3..]);
Ok(unnamed.into_boxed_slice())
} else {
Ok(decompressed.into_boxed_slice())
}
}
}
}
#[inline]
pub fn read_remaining_bytes(
read: &mut (impl Read + ?Sized),
bound: usize,
) -> Result<Box<[u8]>, ReadingError> {
let mut return_buf = Vec::with_capacity(bound.min(1024));
// Take one extra byte to check for exceeding bound
read.take(bound as u64 + 1)
.read_to_end(&mut return_buf)
.map_err(|err| ReadingError::Incomplete(err.to_string()))?;
if return_buf.len() > bound {
return Err(ReadingError::TooLarge("Read remaining too long".into()));
}
Ok(return_buf.into_boxed_slice())
}
pub trait NetworkWriteExt {
fn write_component(
&mut self,
component: &TextComponent,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError> {
if *version < JavaMinecraftVersion::V_1_20_3 {
let json = component.to_json_for_version(version);
let max_len = if *version >= JavaMinecraftVersion::V_1_13 {
262144
} else {
32767
};
self.write_string_bounded(&json, max_len)
} else {
self.write_slice(&component.encode_for_version(version))
}
}
fn write_i8(&mut self, data: i8) -> Result<(), WritingError>;
fn write_u8(&mut self, data: u8) -> Result<(), WritingError>;
fn write_i16_be(&mut self, data: i16) -> Result<(), WritingError>;
fn write_u16_be(&mut self, data: u16) -> Result<(), WritingError>;
fn write_i32_be(&mut self, data: i32) -> Result<(), WritingError>;
fn write_u32_be(&mut self, data: u32) -> Result<(), WritingError>;
fn write_i64_be(&mut self, data: i64) -> Result<(), WritingError>;
fn write_u64_be(&mut self, data: u64) -> Result<(), WritingError>;
fn write_f32_be(&mut self, data: f32) -> Result<(), WritingError>;
fn write_f64_be(&mut self, data: f64) -> Result<(), WritingError>;
fn write_slice(&mut self, data: &[u8]) -> Result<(), WritingError>;
fn write_i16(&mut self, data: i16) -> Result<(), WritingError> {
self.write_i16_be(data)
}
fn write_u16(&mut self, data: u16) -> Result<(), WritingError> {
self.write_u16_be(data)
}
fn write_i32(&mut self, data: i32) -> Result<(), WritingError> {
self.write_i32_be(data)
}
fn write_u32(&mut self, data: u32) -> Result<(), WritingError> {
self.write_u32_be(data)
}
fn write_i64(&mut self, data: i64) -> Result<(), WritingError> {
self.write_i64_be(data)
}
fn write_u64(&mut self, data: u64) -> Result<(), WritingError> {
self.write_u64_be(data)
}
fn write_f32(&mut self, data: f32) -> Result<(), WritingError> {
self.write_f32_be(data)
}
fn write_f64(&mut self, data: f64) -> Result<(), WritingError> {
self.write_f64_be(data)
}
fn put_var_int(&mut self, data: &VarInt) -> Result<(), WritingError> {
self.write_var_int(data)
}
fn put_i32(&mut self, data: i32) -> Result<(), WritingError> {
self.write_i32(data)
}
fn put_bool(&mut self, data: bool) -> Result<(), WritingError> {
self.write_bool(data)
}
fn write_bool(&mut self, data: bool) -> Result<(), WritingError> {
if data {
self.write_u8(1)
} else {
self.write_u8(0)
}
}
fn write_fixed_bitset(&mut self, bits: usize, bit_set: FixedBitSet)
-> Result<(), WritingError>;
fn write_var_int(&mut self, data: &VarInt) -> Result<(), WritingError>;
fn write_var_uint(&mut self, data: &VarUInt) -> Result<(), WritingError>;
fn write_var_long(&mut self, data: &VarLong) -> Result<(), WritingError>;
fn write_string_bounded(&mut self, data: &str, bound: usize) -> Result<(), WritingError>;
fn write_string(&mut self, data: &str) -> Result<(), WritingError>;
fn write_block_pos(
&mut self,
pos: &BlockPos,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError>;
#[inline]
fn write_container_id(
&mut self,
container_id: &VarInt,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError> {
if *version >= JavaMinecraftVersion::V_1_21_2 {
self.write_var_int(container_id)
} else {
self.write_u8(container_id.0 as u8)
}
}
fn write_uuid(&mut self, data: &uuid::Uuid) -> Result<(), WritingError> {
let (first, second) = data.as_u64_pair();
self.write_u64_be(first)?;
self.write_u64_be(second)
}
fn write_bitset(&mut self, bitset: &BitSet) -> Result<(), WritingError>;
fn write_option<G>(
&mut self,
data: &Option<G>,
writer: impl FnOnce(&mut Self, &G) -> Result<(), WritingError>,
) -> Result<(), WritingError> {
if let Some(data) = data {
self.write_bool(true)?;
writer(self, data)
} else {
self.write_bool(false)
}
}
fn write_list<G>(
&mut self,
list: &[G],
writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>,
) -> Result<(), WritingError> {
self.write_var_int(&(list.len() as i32).into())?;
for data in list {
writer(self, data)?;
}
Ok(())
}
fn write_nbt_with_version(
&mut self,
data: Option<&NbtTag>,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError>;
#[inline]
fn write_compound_nbt_with_version(
&mut self,
data: Option<&NbtCompound>,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError> {
let tag = data.map(|c| NbtTag::Compound(c.clone()));
self.write_nbt_with_version(tag.as_ref(), version)
}
#[inline]
fn write_nbt(&mut self, data: NbtTag) -> Result<(), WritingError> {
self.write_nbt_with_version(Some(&data), &JavaMinecraftVersion::V_26_2)
}
}
macro_rules! write_number_be {
($name:ident, $type:ty) => {
fn $name(&mut self, data: $type) -> Result<(), WritingError> {
self.write_all(&data.to_be_bytes())
.map_err(WritingError::IoError)
}
};
}
impl<W: Write> NetworkWriteExt for W {
fn write_i8(&mut self, data: i8) -> Result<(), WritingError> {
self.write_all(&data.to_be_bytes())
.map_err(WritingError::IoError)
}
fn write_u8(&mut self, data: u8) -> Result<(), WritingError> {
self.write_all(&data.to_be_bytes())
.map_err(WritingError::IoError)
}
write_number_be!(write_i16_be, i16);
write_number_be!(write_u16_be, u16);
write_number_be!(write_i32_be, i32);
write_number_be!(write_u32_be, u32);
write_number_be!(write_i64_be, i64);
write_number_be!(write_u64_be, u64);
write_number_be!(write_f32_be, f32);
write_number_be!(write_f64_be, f64);
fn write_slice(&mut self, data: &[u8]) -> Result<(), WritingError> {
self.write_all(data).map_err(WritingError::IoError)
}
fn write_fixed_bitset(
&mut self,
bits: usize,
bit_set: FixedBitSet,
) -> Result<(), WritingError> {
let new_length = bits.div_ceil(8);
let bytes_to_copy = std::cmp::min(bit_set.len(), new_length);
self.write_slice(&bit_set[..bytes_to_copy])?;
if new_length > bytes_to_copy {
const ZEROES: [u8; 64] = [0u8; 64];
let padding = new_length - bytes_to_copy;
let mut remaining = padding;
while remaining > 0 {
let chunk = remaining.min(ZEROES.len());
self.write_slice(&ZEROES[..chunk])?;
remaining -= chunk;
}
}
Ok(())
}
fn write_var_int(&mut self, data: &VarInt) -> Result<(), WritingError> {
data.encode(self)
}
fn write_var_uint(&mut self, data: &VarUInt) -> Result<(), WritingError> {
data.encode(self)
}
fn write_var_long(&mut self, data: &VarLong) -> Result<(), WritingError> {
data.encode(self)
}
fn write_string_bounded(&mut self, data: &str, bound: usize) -> Result<(), WritingError> {
if data.len() > bound {
return Err(WritingError::Message(format!(
"string length {} exceeds bound {}",
data.len(),
bound
)));
}
self.write_var_int(&data.len().try_into().map_err(|_| {
WritingError::Message(format!("{} isn't representable as a VarInt", data.len()))
})?)?;
self.write_all(data.as_bytes())
.map_err(WritingError::IoError)
}
fn write_string(&mut self, data: &str) -> Result<(), WritingError> {
self.write_string_bounded(data, i16::MAX as usize)
}
fn write_block_pos(
&mut self,
pos: &BlockPos,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError> {
self.write_i64_be(pos.as_long_for_version(version))
}
fn write_bitset(&mut self, data: &BitSet) -> Result<(), WritingError> {
data.encode(self)
}
fn write_option<G>(
&mut self,
data: &Option<G>,
writer: impl FnOnce(&mut Self, &G) -> Result<(), WritingError>,
) -> Result<(), WritingError> {
if let Some(data) = data {
self.write_bool(true)?;
writer(self, data)
} else {
self.write_bool(false)
}
}
fn write_list<G>(
&mut self,
list: &[G],
writer: impl Fn(&mut Self, &G) -> Result<(), WritingError>,
) -> Result<(), WritingError> {
self.write_var_int(&VarInt(list.len() as i32))?;
for data in list {
writer(self, data)?;
}
Ok(())
}
fn write_nbt_with_version(
&mut self,
data: Option<&NbtTag>,
version: &JavaMinecraftVersion,
) -> Result<(), WritingError> {
if *version >= JavaMinecraftVersion::V_1_8 {
if let Some(tag) = data
&& !matches!(tag, NbtTag::End)
{
if *version < JavaMinecraftVersion::V_1_20_2 {
self.write_u8(tag.get_type_id())?;
self.write_u16_be(0)?; // empty root name
let mut write_adaptor = NbtWriteHelperJava::new(self);
tag.clone()
.serialize_data(&mut write_adaptor)
.map_err(|e| WritingError::Message(e.to_string()))?;
} else {
let mut write_adaptor = NbtWriteHelperJava::new(self);
tag.clone()
.serialize(&mut write_adaptor)
.map_err(|e| WritingError::Message(e.to_string()))?;
}
} else {
self.write_u8(pumpkin_nbt::END_ID)?;
}
} else {
// <= 1.7.10
if let Some(tag) = data
&& !matches!(tag, NbtTag::End)
{
let mut buf = Vec::new();
buf.push(tag.get_type_id());
buf.extend_from_slice(&[0x00, 0x00]); // empty root name
let mut write_adaptor = NbtWriteHelperJava::new(&mut buf);
tag.clone()
.serialize_data(&mut write_adaptor)
.map_err(|e| WritingError::Message(e.to_string()))?;
let mut encoder =
flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
encoder.write_all(&buf).map_err(WritingError::IoError)?;
let compressed = encoder.finish().map_err(WritingError::IoError)?;
self.write_i16_be(compressed.len() as i16)?;
self.write_slice(&compressed)?;
} else {
self.write_i16_be(-1)?;
}
}
Ok(())
}
}