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(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(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 { 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, 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, 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 = buf.into_iter().map(|b| b as i8).collect(); Ok(Cow::Owned(i8_buf)) } } pub trait NetworkReadExt { fn get_i8(&mut self) -> Result; fn get_u8(&mut self) -> Result; fn get_i16_be(&mut self) -> Result; fn get_u16_be(&mut self) -> Result; fn get_i32_be(&mut self) -> Result; fn get_u32_be(&mut self) -> Result; fn get_i64_be(&mut self) -> Result; fn get_u64_be(&mut self) -> Result; fn get_f32_be(&mut self) -> Result; fn get_f64_be(&mut self) -> Result; fn get_i128_be(&mut self) -> Result; fn get_u128_be(&mut self) -> Result; #[inline] fn get_i16(&mut self) -> Result { self.get_i16_be() } #[inline] fn get_u16(&mut self) -> Result { self.get_u16_be() } #[inline] fn get_i32(&mut self) -> Result { self.get_i32_be() } #[inline] fn get_u32(&mut self) -> Result { self.get_u32_be() } #[inline] fn get_i64(&mut self) -> Result { self.get_i64_be() } #[inline] fn get_u64(&mut self) -> Result { self.get_u64_be() } #[inline] fn get_f32(&mut self) -> Result { self.get_f32_be() } #[inline] fn get_f64(&mut self) -> Result { self.get_f64_be() } fn read_bytes_to_buf(&mut self, buf: &mut [u8]) -> Result<(), ReadingError>; fn get_bool(&mut self) -> Result; fn get_var_int(&mut self) -> Result; fn get_var_uint(&mut self) -> Result; fn get_var_long(&mut self) -> Result; fn get_var_ulong(&mut self) -> Result; fn get_str_bounded(&mut self, bound: usize) -> Result, ReadingError>; #[inline] fn get_str(&mut self) -> Result, ReadingError> { self.get_str_bounded(32767) } fn get_uuid(&mut self) -> Result; fn get_fixed_bitset(&mut self, bits: usize) -> Result; #[inline] fn get_block_pos(&mut self, version: &JavaMinecraftVersion) -> Result { let val = self.get_i64_be()?; Ok(BlockPos::from_long_for_version(val, version)) } #[inline] fn get_container_id(&mut self, version: &JavaMinecraftVersion) -> Result { if *version >= JavaMinecraftVersion::V_1_21_2 { self.get_var_int() } else { Ok(VarInt(i32::from(self.get_u8()?))) } } #[inline] fn get_option( &mut self, parse: impl FnOnce(&mut Self) -> Result, ) -> Result, ReadingError> { if self.get_bool()? { Ok(Some(parse(self)?)) } else { Ok(None) } } #[inline] fn get_list( &mut self, parse: impl Fn(&mut Self) -> Result, ) -> Result, 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, 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, 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; #[inline] fn get_component( &mut self, version: &JavaMinecraftVersion, ) -> Result { 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, ReadingError>; #[inline] fn get_nbt(&mut self, version: &JavaMinecraftVersion) -> Result, ReadingError> { self.get_nbt_borrowed(version) } #[inline] fn get_compound_nbt_borrowed( &mut self, version: &JavaMinecraftVersion, ) -> Result, 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, ReadingError> { self.get_compound_nbt_borrowed(version) } #[inline] fn read_cow_slice_borrowed(&mut self, count: usize) -> Result, ReadingError> { Ok(Cow::Borrowed(self.read_slice_borrowed(count)?)) } #[inline] fn read_remaining_to_cow_slice_borrowed( &mut self, bound: usize, ) -> Result, ReadingError> { Ok(Cow::Borrowed(self.read_remaining_slice_borrowed(bound)?)) } #[inline] fn get_cow_str_borrowed(&mut self) -> Result, ReadingError> { self.get_cow_str_bounded_borrowed(32767) } #[inline] fn get_cow_str_bounded_borrowed(&mut self, bound: usize) -> Result, 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 { 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, 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 { (**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, 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 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 { let byte = self.get_u8()?; Ok(byte != 0) } #[inline] fn get_var_int(&mut self) -> Result { VarInt::decode(self) } #[inline] fn get_var_uint(&mut self) -> Result { VarUInt::decode(self) } #[inline] fn get_var_long(&mut self) -> Result { VarLong::decode(self) } #[inline] fn get_var_ulong(&mut self) -> Result { VarULong::decode(self) } #[inline] fn get_str_bounded(&mut self, bound: usize) -> Result, 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 { 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 { 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 = 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, 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, 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( &mut self, data: &Option, 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( &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 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( &mut self, data: &Option, 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( &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(()) } }