diff options
Diffstat (limited to 'azalea-protocol')
36 files changed, 1782 insertions, 879 deletions
diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index c9883195..37df8697 100644..100755 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -10,11 +10,15 @@ async-compression = {version = "^0.3.8", features = ["tokio", "zlib"]} async-recursion = "^0.3.2" async-trait = "0.1.51" azalea-auth = {path = "../azalea-auth"} +azalea-brigadier = {path = "../azalea-brigadier"} azalea-chat = {path = "../azalea-chat"} azalea-core = {path = "../azalea-core"} azalea-nbt = {path = "../azalea-nbt"} byteorder = "^1.4.3" bytes = "^1.1.0" +num-derive = "^0.3.3" +num-traits = "^0.2.14" +packet-macros = {path = "./packet-macros"} serde = {version = "1.0.130", features = ["serde_derive"]} serde_json = "^1.0.72" thiserror = "^1.0.30" diff --git a/azalea-protocol/README.md b/azalea-protocol/README.md new file mode 100755 index 00000000..6063ed39 --- /dev/null +++ b/azalea-protocol/README.md @@ -0,0 +1,3 @@ +# Azalea Protocol + +Sent and receive Minecraft packets. diff --git a/azalea-protocol/packet-macros/Cargo.toml b/azalea-protocol/packet-macros/Cargo.toml new file mode 100755 index 00000000..2c0f36d7 --- /dev/null +++ b/azalea-protocol/packet-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "packet-macros" +version = "0.1.0" + +[lib] +proc-macro = true +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "^1.0.36" +quote = "^1.0.10" +syn = "^1.0.82" diff --git a/azalea-protocol/packet-macros/src/lib.rs b/azalea-protocol/packet-macros/src/lib.rs new file mode 100755 index 00000000..45df7e81 --- /dev/null +++ b/azalea-protocol/packet-macros/src/lib.rs @@ -0,0 +1,300 @@ +use proc_macro::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + self, braced, + parse::{Parse, ParseStream, Result}, + parse_macro_input, DeriveInput, FieldsNamed, Ident, LitInt, Token, +}; + +fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream { + let DeriveInput { ident, data, .. } = parse_macro_input!(input); + + let fields = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => fields, + _ => panic!("#[derive(*Packet)] can only be used on structs"), + }; + let FieldsNamed { named, .. } = match fields { + syn::Fields::Named(f) => f, + _ => panic!("#[derive(*Packet)] can only be used on structs with named fields"), + }; + + let write_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) => { + if f.attrs.iter().any(|attr| attr.path.is_ident("varint")) { + quote! { + crate::mc_buf::McBufVarintWritable::varint_write_into(&self.#field_name, buf)?; + } + } else { + quote! { + crate::mc_buf::McBufWritable::write_into(&self.#field_name, buf)?; + } + } + } + _ => panic!( + "Error writing field {}: {}", + field_name.clone().unwrap(), + field_type.to_token_stream() + ), + } + }) + .collect::<Vec<_>>(); + + let read_fields = named + .iter() + .map(|f| { + let field_name = &f.ident; + let field_type = &f.ty; + // do a different buf.write_* for each field depending on the type + // if it's a string, use buf.write_string + match field_type { + syn::Type::Path(_) => { + if f.attrs.iter().any(|a| a.path.is_ident("varint")) { + quote! { + let #field_name = crate::mc_buf::McBufVarintReadable::varint_read_into(buf).await?; + } + } else { + quote! { + let #field_name = crate::mc_buf::McBufReadable::read_into(buf).await?; + } + } + } + _ => panic!( + "Error reading field {}: {}", + field_name.clone().unwrap(), + field_type.to_token_stream() + ), + } + }) + .collect::<Vec<_>>(); + let read_field_names = named.iter().map(|f| &f.ident).collect::<Vec<_>>(); + + quote! { + impl #ident { + pub fn get(self) -> #state { + #state::#ident(self) + } + + pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + #(#write_fields)* + Ok(()) + } + + pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( + buf: &mut T, + ) -> Result<#state, String> { + #(#read_fields)* + Ok(#ident { + #(#read_field_names: #read_field_names),* + }.get()) + } + } + } + .into() +} + +#[proc_macro_derive(GamePacket, attributes(varint))] +pub fn derive_game_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::game::GamePacket}) +} + +#[proc_macro_derive(HandshakePacket, attributes(varint))] +pub fn derive_handshake_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::handshake::HandshakePacket}) +} + +#[proc_macro_derive(LoginPacket, attributes(varint))] +pub fn derive_login_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::login::LoginPacket}) +} + +#[proc_macro_derive(StatusPacket, attributes(varint))] +pub fn derive_status_packet(input: TokenStream) -> TokenStream { + as_packet_derive(input, quote! {crate::packets::status::StatusPacket}) +} + +#[derive(Debug)] +struct PacketIdPair { + id: u32, + module: Ident, + name: Ident, +} +#[derive(Debug)] +struct PacketIdMap { + packets: Vec<PacketIdPair>, +} + +impl Parse for PacketIdMap { + fn parse(input: ParseStream) -> Result<Self> { + let mut packets = vec![]; + loop { + // 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, + // 0x0e + let packet_id: LitInt = match input.parse() { + Ok(i) => i, + Err(_) => break, + }; + let packet_id = packet_id.base10_parse::<u32>()?; + // : + input.parse::<Token![:]>()?; + // clientbound_change_difficulty_packet + let module: Ident = input.parse()?; + // :: + input.parse::<Token![::]>()?; + // ClientboundChangeDifficultyPacket + let name: Ident = input.parse()?; + + packets.push(PacketIdPair { + id: packet_id, + module, + name, + }); + + if input.parse::<Token![,]>().is_err() { + break; + } + } + + Ok(PacketIdMap { packets }) + } +} + +#[derive(Debug)] +struct DeclareStatePackets { + name: Ident, + serverbound: PacketIdMap, + clientbound: PacketIdMap, +} + +impl Parse for DeclareStatePackets { + fn parse(input: ParseStream) -> Result<Self> { + let name = input.parse()?; + input.parse::<Token![,]>()?; + + let serverbound_token: Ident = input.parse()?; + if serverbound_token != "Serverbound" { + return Err(syn::Error::new( + serverbound_token.span(), + "Expected `Serverbound`", + )); + } + input.parse::<Token![=>]>()?; + let content; + braced!(content in input); + let serverbound = content.parse()?; + + input.parse::<Token![,]>()?; + + let clientbound_token: Ident = input.parse()?; + if clientbound_token != "Clientbound" { + return Err(syn::Error::new( + clientbound_token.span(), + "Expected `Clientbound`", + )); + } + input.parse::<Token![=>]>()?; + let content; + braced!(content in input); + let clientbound = content.parse()?; + + Ok(DeclareStatePackets { + name, + serverbound, + clientbound, + }) + } +} +#[proc_macro] +pub fn declare_state_packets(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeclareStatePackets); + + let state_name = input.name; + let state_name_litstr = syn::LitStr::new(&state_name.to_string(), state_name.span()); + + let mut enum_contents = quote!(); + let mut id_match_contents = quote!(); + let mut write_match_contents = quote!(); + let mut serverbound_read_match_contents = quote!(); + let mut clientbound_read_match_contents = quote!(); + for PacketIdPair { id, module, name } in input.serverbound.packets { + enum_contents.extend(quote! { + #name(#module::#name), + }); + id_match_contents.extend(quote! { + #state_name::#name(_packet) => #id, + }); + write_match_contents.extend(quote! { + #state_name::#name(packet) => packet.write(buf), + }); + serverbound_read_match_contents.extend(quote! { + #id => #module::#name::read(buf).await?, + }); + } + for PacketIdPair { id, module, name } in input.clientbound.packets { + enum_contents.extend(quote! { + #name(#module::#name), + }); + id_match_contents.extend(quote! { + #state_name::#name(_packet) => #id, + }); + write_match_contents.extend(quote! { + #state_name::#name(packet) => packet.write(buf), + }); + clientbound_read_match_contents.extend(quote! { + #id => #module::#name::read(buf).await?, + }); + } + + quote! { + #[derive(Clone, Debug)] + pub enum #state_name + where + Self: Sized, + { + #enum_contents + } + + #[async_trait::async_trait] + impl crate::packets::ProtocolPacket for #state_name { + fn id(&self) -> u32 { + match self { + #id_match_contents + } + } + + fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + match self { + #write_match_contents + } + } + + /// Read a packet by its id, ConnectionProtocol, and flow + async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( + id: u32, + flow: &crate::connect::PacketFlow, + buf: &mut T, + ) -> Result<#state_name, String> + where + Self: Sized, + { + Ok(match flow { + crate::connect::PacketFlow::ServerToClient => match id { + #clientbound_read_match_contents + _ => panic!("Unknown ServerToClient {} packet id: {}", #state_name_litstr, id), + }, + crate::connect::PacketFlow::ClientToServer => match id { + #serverbound_read_match_contents + _ => return Err(format!("Unknown ClientToServer {} packet id: {}", #state_name_litstr, id)), + }, + }) + } + } + } + .into() +} diff --git a/azalea-protocol/src/connect.rs b/azalea-protocol/src/connect.rs index 3d910d3a..3d910d3a 100644..100755 --- a/azalea-protocol/src/connect.rs +++ b/azalea-protocol/src/connect.rs diff --git a/azalea-protocol/src/lib.rs b/azalea-protocol/src/lib.rs index 684add45..684add45 100644..100755 --- a/azalea-protocol/src/lib.rs +++ b/azalea-protocol/src/lib.rs diff --git a/azalea-protocol/src/mc_buf.rs b/azalea-protocol/src/mc_buf.rs deleted file mode 100644 index 538fc212..00000000 --- a/azalea-protocol/src/mc_buf.rs +++ /dev/null @@ -1,478 +0,0 @@ -//! Utilities for reading and writing for the Minecraft protocol - -use std::io::Write; - -use async_trait::async_trait; -use azalea_core::resource_location::ResourceLocation; -use byteorder::{BigEndian, WriteBytesExt}; -use tokio::io::{AsyncRead, AsyncReadExt}; - -// const DEFAULT_NBT_QUOTA: u32 = 2097152; -const MAX_STRING_LENGTH: u16 = 32767; -// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144; - -#[async_trait] -pub trait Writable { - fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> - where - F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, - T: Sized, - Self: Sized; - fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error>; - fn write_map<KF, VF, KT, VT>( - &mut self, - map: Vec<(KT, VT)>, - key_writer: KF, - value_writer: VF, - ) -> Result<(), std::io::Error> - where - KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, - VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, - Self: Sized; - - fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error>; - fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; - fn write_varint(&mut self, value: i32) -> Result<(), std::io::Error>; - fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error>; - fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error>; - fn write_short(&mut self, n: u16) -> Result<(), std::io::Error>; - fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; - fn write_int(&mut self, n: i32) -> Result<(), std::io::Error>; - fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error>; - fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>; - fn write_long(&mut self, n: i64) -> Result<(), std::io::Error>; - fn write_resource_location( - &mut self, - location: &ResourceLocation, - ) -> Result<(), std::io::Error>; -} - -#[async_trait] -impl Writable for Vec<u8> { - fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> - where - F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, - Self: Sized, - { - self.write_varint(list.len() as i32)?; - for item in list { - writer(self, item)?; - } - Ok(()) - } - - fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error> { - self.write_list(&list, |buf, n| buf.write_varint(*n)) - } - - fn write_map<KF, VF, KT, VT>( - &mut self, - map: Vec<(KT, VT)>, - key_writer: KF, - value_writer: VF, - ) -> Result<(), std::io::Error> - where - KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, - VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, - Self: Sized, - { - self.write_varint(map.len() as i32)?; - for (key, value) in map { - key_writer(self, key)?; - value_writer(self, value)?; - } - Ok(()) - } - - fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error> { - WriteBytesExt::write_u8(self, n) - } - - fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { - self.extend_from_slice(bytes); - Ok(()) - } - - fn write_varint(&mut self, mut value: i32) -> Result<(), std::io::Error> { - let mut buffer = [0]; - if value == 0 { - self.write_all(&buffer).unwrap(); - } - while value != 0 { - buffer[0] = (value & 0b0111_1111) as u8; - value = (value >> 7) & (i32::max_value() >> 6); - if value != 0 { - buffer[0] |= 0b1000_0000; - } - self.write_all(&buffer)?; - } - Ok(()) - } - - fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error> { - if string.len() > len { - panic!( - "String too big (was {} bytes encoded, max {})", - string.len(), - len - ); - } - self.write_varint(string.len() as i32)?; - self.write_bytes(string.as_bytes()) - } - - fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error> { - self.write_utf_with_len(string, MAX_STRING_LENGTH.into()) - } - - fn write_short(&mut self, n: u16) -> Result<(), std::io::Error> { - WriteBytesExt::write_u16::<BigEndian>(self, n) - } - - fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { - self.write_varint(bytes.len() as i32)?; - self.write_bytes(bytes) - } - - fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> { - WriteBytesExt::write_i32::<BigEndian>(self, n) - } - - fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error> { - self.write_byte(if b { 1 } else { 0 }) - } - - fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error> { - nbt.write(self) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) - } - - fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> { - WriteBytesExt::write_i64::<BigEndian>(self, n) - } - - fn write_resource_location( - &mut self, - location: &ResourceLocation, - ) -> Result<(), std::io::Error> { - self.write_utf(&location.to_string()) - } -} - -#[async_trait] -pub trait Readable { - async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String>; - async fn read_varint(&mut self) -> Result<i32, String>; - fn get_varint_size(&mut self, value: i32) -> u8; - fn get_varlong_size(&mut self, value: i32) -> u8; - async fn read_byte_array(&mut self) -> Result<Vec<u8>, String>; - async fn read_bytes(&mut self, n: usize) -> Result<Vec<u8>, String>; - async fn read_utf(&mut self) -> Result<String, String>; - async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String>; - async fn read_byte(&mut self) -> Result<u8, String>; - async fn read_int(&mut self) -> Result<i32, String>; - async fn read_boolean(&mut self) -> Result<bool, String>; - async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String>; - async fn read_long(&mut self) -> Result<i64, String>; - async fn read_resource_location(&mut self) -> Result<ResourceLocation, String>; -} - -#[async_trait] -impl<R> Readable for R -where - R: AsyncRead + std::marker::Unpin + std::marker::Send, -{ - async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String> { - let len = self.read_varint().await?; - let mut list = Vec::with_capacity(len as usize); - for _ in 0..len { - list.push(self.read_varint().await?); - } - Ok(list) - } - - // fast varints stolen from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67 - /// Read a single varint from the reader and return the value, along with the number of bytes read - async fn read_varint(&mut self) -> Result<i32, String> { - let mut buffer = [0]; - let mut ans = 0; - for i in 0..4 { - self.read_exact(&mut buffer) - .await - .map_err(|_| "Invalid VarInt".to_string())?; - ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i); - if buffer[0] & 0b1000_0000 == 0 { - return Ok(ans); - } - } - Ok(ans) - } - - fn get_varint_size(&mut self, value: i32) -> u8 { - for i in 1..5 { - if (value & -1 << (i * 7)) != 0 { - continue; - } - return i; - } - 5 - } - - fn get_varlong_size(&mut self, value: i32) -> u8 { - for i in 1..10 { - if (value & -1 << (i * 7)) != 0 { - continue; - } - return i; - } - 10 - } - - async fn read_byte_array(&mut self) -> Result<Vec<u8>, String> { - let length = self.read_varint().await? as usize; - Ok(self.read_bytes(length).await?) - } - - async fn read_bytes(&mut self, n: usize) -> Result<Vec<u8>, String> { - let mut bytes = vec![0; n]; - match AsyncReadExt::read_exact(self, &mut bytes).await { - Ok(_) => Ok(bytes), - Err(_) => Err("Error reading bytes".to_string()), - } - } - - async fn read_utf(&mut self) -> Result<String, String> { - self.read_utf_with_len(MAX_STRING_LENGTH.into()).await - } - - async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String> { - let length = self.read_varint().await?; - // i don't know why it's multiplied by 4 but it's like that in mojang's code so - if length < 0 { - return Err( - "The received encoded string buffer length is less than zero! Weird string!" - .to_string(), - ); - } - if length as u32 > max_length * 4 { - return Err(format!( - "The received encoded string buffer length is longer than maximum allowed ({} > {})", - length, - max_length * 4 - )); - } - - // this is probably quite inefficient, idk how to do it better - let mut string = String::new(); - let mut buffer = vec![0; length as usize]; - self.read_exact(&mut buffer) - .await - .map_err(|_| "Invalid UTF-8".to_string())?; - - string.push_str(std::str::from_utf8(&buffer).unwrap()); - if string.len() > length as usize { - return Err(format!( - "The received string length is longer than maximum allowed ({} > {})", - length, max_length - )); - } - - Ok(string) - } - - /// Read a single byte from the reader - async fn read_byte(&mut self) -> Result<u8, String> { - match AsyncReadExt::read_u8(self).await { - Ok(r) => Ok(r), - Err(_) => Err("Error reading byte".to_string()), - } - } - - async fn read_int(&mut self) -> Result<i32, String> { - match AsyncReadExt::read_i32(self).await { - Ok(r) => Ok(r), - Err(_) => Err("Error reading int".to_string()), - } - } - - async fn read_boolean(&mut self) -> Result<bool, String> { - match self.read_byte().await { - Ok(0) => Ok(false), - Ok(1) => Ok(true), - _ => Err("Error reading boolean".to_string()), - } - } - - async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String> { - match azalea_nbt::Tag::read(self).await { - Ok(r) => Ok(r), - // Err(e) => Err(e.to_string()), - Err(e) => Err(e.to_string()).unwrap(), - } - } - - async fn read_long(&mut self) -> Result<i64, String> { - match AsyncReadExt::read_i64(self).await { - Ok(r) => Ok(r), - Err(_) => Err("Error reading long".to_string()), - } - } - - async fn read_resource_location(&mut self) -> Result<ResourceLocation, String> { - // get the resource location from the string - let location_string = self.read_utf().await?; - let location = ResourceLocation::new(&location_string)?; - Ok(location) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::{collections::HashMap, io::Cursor}; - use tokio::io::BufReader; - - #[test] - fn test_write_varint() { - let mut buf = Vec::new(); - buf.write_varint(123456).unwrap(); - assert_eq!(buf, vec![192, 196, 7]); - - let mut buf = Vec::new(); - buf.write_varint(0).unwrap(); - assert_eq!(buf, vec![0]); - } - - #[tokio::test] - async fn test_read_varint() { - let mut buf = BufReader::new(Cursor::new(vec![192, 196, 7])); - assert_eq!(buf.read_varint().await.unwrap(), 123456); - assert_eq!(buf.get_varint_size(123456), 3); - - let mut buf = BufReader::new(Cursor::new(vec![0])); - assert_eq!(buf.read_varint().await.unwrap(), 0); - assert_eq!(buf.get_varint_size(0), 1); - - let mut buf = BufReader::new(Cursor::new(vec![1])); - assert_eq!(buf.read_varint().await.unwrap(), 1); - assert_eq!(buf.get_varint_size(1), 1); - } - - #[tokio::test] - async fn test_read_varint_longer() { - let mut buf = BufReader::new(Cursor::new(vec![138, 56, 0, 135, 56, 123])); - assert_eq!(buf.read_varint().await.unwrap(), 7178); - } - - #[tokio::test] - async fn test_list() { - let mut buf = Vec::new(); - buf.write_list(&vec!["a", "bc", "def"], |buf, s| buf.write_utf(s)) - .unwrap(); - - // there's no read_list because idk how to do it in rust - let mut buf = BufReader::new(Cursor::new(buf)); - - let mut result = Vec::new(); - let length = buf.read_varint().await.unwrap(); - for _ in 0..length { - result.push(buf.read_utf().await.unwrap()); - } - - assert_eq!(result, vec!["a", "bc", "def"]); - } - - #[tokio::test] - async fn test_int_id_list() { - let mut buf = Vec::new(); - buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i)) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - let result = buf.read_int_id_list().await.unwrap(); - assert_eq!(result, vec![1, 2, 3]); - } - - #[tokio::test] - async fn test_map() { - let mut buf = Vec::new(); - buf.write_map( - vec![("a", 1), ("bc", 23), ("def", 456)], - Vec::write_utf, - Vec::write_varint, - ) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - let mut result = Vec::new(); - let length = buf.read_varint().await.unwrap(); - for _ in 0..length { - result.push(( - buf.read_utf().await.unwrap(), - buf.read_varint().await.unwrap(), - )); - } - - assert_eq!( - result, - vec![ - ("a".to_string(), 1), - ("bc".to_string(), 23), - ("def".to_string(), 456) - ] - ); - } - - #[tokio::test] - async fn test_nbt() { - let mut buf = Vec::new(); - buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "hello world".to_string(), - azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "name".to_string(), - azalea_nbt::Tag::String("Bananrama".to_string()), - )])), - )]))) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - let result = buf.read_nbt().await.unwrap(); - assert_eq!( - result, - azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "hello world".to_string(), - azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( - "name".to_string(), - azalea_nbt::Tag::String("Bananrama".to_string()), - )])), - )])) - ); - } - - #[tokio::test] - async fn test_long() { - let mut buf = Vec::new(); - buf.write_long(123456).unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - assert_eq!(buf.read_long().await.unwrap(), 123456); - } - - #[tokio::test] - async fn test_resource_location() { - let mut buf = Vec::new(); - buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap()) - .unwrap(); - - let mut buf = BufReader::new(Cursor::new(buf)); - - assert_eq!( - buf.read_resource_location().await.unwrap(), - ResourceLocation::new("minecraft:dirt").unwrap() - ); - } -} diff --git a/azalea-protocol/src/mc_buf/mod.rs b/azalea-protocol/src/mc_buf/mod.rs new file mode 100755 index 00000000..4ecb65d1 --- /dev/null +++ b/azalea-protocol/src/mc_buf/mod.rs @@ -0,0 +1,231 @@ +//! Utilities for reading and writing for the Minecraft protocol + +mod read; +mod write; + +pub use read::{McBufReadable, McBufVarintReadable, Readable}; +pub use write::{McBufVarintWritable, McBufWritable, Writable}; + +// const DEFAULT_NBT_QUOTA: u32 = 2097152; +const MAX_STRING_LENGTH: u16 = 32767; +// const MAX_COMPONENT_STRING_LENGTH: u32 = 262144; + +#[cfg(test)] +mod tests { + use super::*; + use azalea_core::resource_location::ResourceLocation; + use std::{collections::HashMap, io::Cursor}; + use tokio::io::BufReader; + + #[test] + fn test_write_varint() { + let mut buf = Vec::new(); + buf.write_varint(0).unwrap(); + assert_eq!(buf, vec![0]); + + let mut buf = Vec::new(); + buf.write_varint(1).unwrap(); + assert_eq!(buf, vec![1]); + + let mut buf = Vec::new(); + buf.write_varint(2).unwrap(); + assert_eq!(buf, vec![2]); + + let mut buf = Vec::new(); + buf.write_varint(127).unwrap(); + assert_eq!(buf, vec![127]); + + let mut buf = Vec::new(); + buf.write_varint(128).unwrap(); + assert_eq!(buf, vec![128, 1]); + + let mut buf = Vec::new(); + buf.write_varint(255).unwrap(); + assert_eq!(buf, vec![255, 1]); + + let mut buf = Vec::new(); + buf.write_varint(25565).unwrap(); + assert_eq!(buf, vec![221, 199, 1]); + + let mut buf = Vec::new(); + buf.write_varint(2097151).unwrap(); + assert_eq!(buf, vec![255, 255, 127]); + + let mut buf = Vec::new(); + buf.write_varint(2147483647).unwrap(); + assert_eq!(buf, vec![255, 255, 255, 255, 7]); + + let mut buf = Vec::new(); + buf.write_varint(-1).unwrap(); + assert_eq!(buf, vec![255, 255, 255, 255, 15]); + + let mut buf = Vec::new(); + buf.write_varint(-2147483648).unwrap(); + assert_eq!(buf, vec![128, 128, 128, 128, 8]); + } + + #[tokio::test] + async fn test_read_varint() { + let mut buf = BufReader::new(Cursor::new(vec![0])); + assert_eq!(buf.read_varint().await.unwrap(), 0); + assert_eq!(buf.get_varint_size(0), 1); + + let mut buf = BufReader::new(Cursor::new(vec![1])); + assert_eq!(buf.read_varint().await.unwrap(), 1); + assert_eq!(buf.get_varint_size(1), 1); + + let mut buf = BufReader::new(Cursor::new(vec![2])); + assert_eq!(buf.read_varint().await.unwrap(), 2); + assert_eq!(buf.get_varint_size(2), 1); + + let mut buf = BufReader::new(Cursor::new(vec![127])); + assert_eq!(buf.read_varint().await.unwrap(), 127); + assert_eq!(buf.get_varint_size(127), 1); + + let mut buf = BufReader::new(Cursor::new(vec![128, 1])); + assert_eq!(buf.read_varint().await.unwrap(), 128); + assert_eq!(buf.get_varint_size(128), 2); + + let mut buf = BufReader::new(Cursor::new(vec![255, 1])); + assert_eq!(buf.read_varint().await.unwrap(), 255); + assert_eq!(buf.get_varint_size(255), 2); + + let mut buf = BufReader::new(Cursor::new(vec![221, 199, 1])); + assert_eq!(buf.read_varint().await.unwrap(), 25565); + assert_eq!(buf.get_varint_size(25565), 3); + + let mut buf = BufReader::new(Cursor::new(vec![255, 255, 127])); + assert_eq!(buf.read_varint().await.unwrap(), 2097151); + assert_eq!(buf.get_varint_size(2097151), 3); + + let mut buf = BufReader::new(Cursor::new(vec![255, 255, 255, 255, 7])); + assert_eq!(buf.read_varint().await.unwrap(), 2147483647); + assert_eq!(buf.get_varint_size(2147483647), 5); + + let mut buf = BufReader::new(Cursor::new(vec![255, 255, 255, 255, 15])); + assert_eq!(buf.read_varint().await.unwrap(), -1); + assert_eq!(buf.get_varint_size(-1), 5); + + let mut buf = BufReader::new(Cursor::new(vec![128, 128, 128, 128, 8])); + assert_eq!(buf.read_varint().await.unwrap(), -2147483648); + assert_eq!(buf.get_varint_size(-2147483648), 5); + } + + #[tokio::test] + async fn test_read_varint_longer() { + let mut buf = BufReader::new(Cursor::new(vec![138, 56, 0, 135, 56, 123])); + assert_eq!(buf.read_varint().await.unwrap(), 7178); + } + + #[tokio::test] + async fn test_list() { + let mut buf = Vec::new(); + buf.write_list(&vec!["a", "bc", "def"], |buf, s| buf.write_utf(s)) + .unwrap(); + + // there's no read_list because idk how to do it in rust + let mut buf = BufReader::new(Cursor::new(buf)); + + let mut result = Vec::new(); + let length = buf.read_varint().await.unwrap(); + for _ in 0..length { + result.push(buf.read_utf().await.unwrap()); + } + + assert_eq!(result, vec!["a", "bc", "def"]); + } + + #[tokio::test] + async fn test_int_id_list() { + let mut buf = Vec::new(); + buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i)) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + let result = buf.read_int_id_list().await.unwrap(); + assert_eq!(result, vec![1, 2, 3]); + } + + #[tokio::test] + async fn test_map() { + let mut buf = Vec::new(); + buf.write_map( + vec![("a", 1), ("bc", 23), ("def", 456)], + Vec::write_utf, + Vec::write_varint, + ) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + let mut result = Vec::new(); + let length = buf.read_varint().await.unwrap(); + for _ in 0..length { + result.push(( + buf.read_utf().await.unwrap(), + buf.read_varint().await.unwrap(), + )); + } + + assert_eq!( + result, + vec![ + ("a".to_string(), 1), + ("bc".to_string(), 23), + ("def".to_string(), 456) + ] + ); + } + + #[tokio::test] + async fn test_nbt() { + let mut buf = Vec::new(); + buf.write_nbt(&azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "hello world".to_string(), + azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "name".to_string(), + azalea_nbt::Tag::String("Bananrama".to_string()), + )])), + )]))) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + let result = buf.read_nbt().await.unwrap(); + assert_eq!( + result, + azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "hello world".to_string(), + azalea_nbt::Tag::Compound(HashMap::from_iter(vec![( + "name".to_string(), + azalea_nbt::Tag::String("Bananrama".to_string()), + )])), + )])) + ); + } + + #[tokio::test] + async fn test_long() { + let mut buf = Vec::new(); + buf.write_long(123456).unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + assert_eq!(buf.read_long().await.unwrap(), 123456); + } + + #[tokio::test] + async fn test_resource_location() { + let mut buf = Vec::new(); + buf.write_resource_location(&ResourceLocation::new("minecraft:dirt").unwrap()) + .unwrap(); + + let mut buf = BufReader::new(Cursor::new(buf)); + + assert_eq!( + buf.read_resource_location().await.unwrap(), + ResourceLocation::new("minecraft:dirt").unwrap() + ); + } +} diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-protocol/src/mc_buf/read.rs new file mode 100755 index 00000000..1e031916 --- /dev/null +++ b/azalea-protocol/src/mc_buf/read.rs @@ -0,0 +1,459 @@ +use async_trait::async_trait; +use azalea_chat::component::Component; +use azalea_core::{ + difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation, +}; +use serde::Deserialize; +use tokio::io::{AsyncRead, AsyncReadExt}; + +use super::MAX_STRING_LENGTH; + +#[async_trait] +pub trait Readable { + async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String>; + async fn read_varint(&mut self) -> Result<i32, String>; + fn get_varint_size(&mut self, value: i32) -> u8; + fn get_varlong_size(&mut self, value: i32) -> u8; + async fn read_byte_array(&mut self) -> Result<Vec<u8>, String>; + async fn read_bytes_with_len(&mut self, n: usize) -> Result<Vec<u8>, String>; + async fn read_bytes(&mut self) -> Result<Vec<u8>, String>; + async fn read_utf(&mut self) -> Result<String, String>; + async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String>; + async fn read_byte(&mut self) -> Result<u8, String>; + async fn read_int(&mut self) -> Result<i32, String>; + async fn read_boolean(&mut self) -> Result<bool, String>; + async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String>; + async fn read_long(&mut self) -> Result<i64, String>; + async fn read_resource_location(&mut self) -> Result<ResourceLocation, String>; + async fn read_short(&mut self) -> Result<i16, String>; + async fn read_float(&mut self) -> Result<f32, String>; +} + +#[async_trait] +impl<R> Readable for R +where + R: AsyncRead + std::marker::Unpin + std::marker::Send, +{ + async fn read_int_id_list(&mut self) -> Result<Vec<i32>, String> { + let len = self.read_varint().await?; + let mut list = Vec::with_capacity(len as usize); + for _ in 0..len { + list.push(self.read_varint().await?); + } + Ok(list) + } + + // fast varints modified from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L67 + /// Read a single varint from the reader and return the value, along with the number of bytes read + async fn read_varint(&mut self) -> Result<i32, String> { + let mut buffer = [0]; + let mut ans = 0; + for i in 0..5 { + self.read_exact(&mut buffer) + .await + .map_err(|_| "Invalid VarInt".to_string())?; + ans |= ((buffer[0] & 0b0111_1111) as i32) << (7 * i); + if buffer[0] & 0b1000_0000 == 0 { + return Ok(ans); + } + } + Ok(ans) + } + + fn get_varint_size(&mut self, value: i32) -> u8 { + for i in 1..5 { + if (value & -1 << (i * 7)) != 0 { + continue; + } + return i; + } + 5 + } + + fn get_varlong_size(&mut self, value: i32) -> u8 { + for i in 1..10 { + if (value & -1 << (i * 7)) != 0 { + continue; + } + return i; + } + 10 + } + + async fn read_byte_array(&mut self) -> Result<Vec<u8>, String> { + let length = self.read_varint().await? as usize; + Ok(self.read_bytes_with_len(length).await?) + } + + async fn read_bytes_with_len(&mut self, n: usize) -> Result<Vec<u8>, String> { + let mut bytes = vec![0; n]; + match AsyncReadExt::read_exact(self, &mut bytes).await { + Ok(_) => Ok(bytes), + Err(_) => Err("Error reading bytes".to_string()), + } + } + + async fn read_bytes(&mut self) -> Result<Vec<u8>, String> { + // read to end of the buffer + let mut bytes = vec![]; + AsyncReadExt::read_to_end(self, &mut bytes) + .await + .map_err(|_| "Error reading bytes".to_string())?; + Ok(bytes) + } + + async fn read_utf(&mut self) -> Result<String, String> { + self.read_utf_with_len(MAX_STRING_LENGTH.into()).await + } + + async fn read_utf_with_len(&mut self, max_length: u32) -> Result<String, String> { + let length = self.read_varint().await?; + // i don't know why it's multiplied by 4 but it's like that in mojang's code so + if length < 0 { + return Err( + "The received encoded string buffer length is less than zero! Weird string!" + .to_string(), + ); + } + if length as u32 > max_length * 4 { + return Err(format!( + "The received encoded string buffer length is longer than maximum allowed ({} > {})", + length, + max_length * 4 + )); + } + + // this is probably quite inefficient, idk how to do it better + let mut string = String::new(); + let mut buffer = vec![0; length as usize]; + self.read_exact(&mut buffer) + .await + .map_err(|_| "Invalid UTF-8".to_string())?; + + string.push_str(std::str::from_utf8(&buffer).unwrap()); + if string.len() > length as usize { + return Err(format!( + "The received string length is longer than maximum allowed ({} > {})", + length, max_length + )); + } + + Ok(string) + } + + /// Read a single byte from the reader + async fn read_byte(&mut self) -> Result<u8, String> { + match AsyncReadExt::read_u8(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading byte".to_string()), + } + } + + async fn read_int(&mut self) -> Result<i32, String> { + match AsyncReadExt::read_i32(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading int".to_string()), + } + } + + async fn read_boolean(&mut self) -> Result<bool, String> { + match self.read_byte().await { + Ok(0) => Ok(false), + Ok(1) => Ok(true), + _ => Err("Error reading boolean".to_string()), + } + } + + async fn read_nbt(&mut self) -> Result<azalea_nbt::Tag, String> { + match azalea_nbt::Tag::read(self).await { + Ok(r) => Ok(r), + // Err(e) => Err(e.to_string()), + Err(e) => Err(e.to_string()).unwrap(), + } + } + + async fn read_long(&mut self) -> Result<i64, String> { + match AsyncReadExt::read_i64(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading long".to_string()), + } + } + + async fn read_resource_location(&mut self) -> Result<ResourceLocation, String> { + // get the resource location from the string + let location_string = self.read_utf().await?; + let location = ResourceLocation::new(&location_string)?; + Ok(location) + } + + async fn read_short(&mut self) -> Result<i16, String> { + match AsyncReadExt::read_i16(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading short".to_string()), + } + } + + async fn read_float(&mut self) -> Result<f32, String> { + match AsyncReadExt::read_f32(self).await { + Ok(r) => Ok(r), + Err(_) => Err("Error reading float".to_string()), + } + } +} + +#[async_trait] +pub trait McBufReadable +where + Self: Sized, +{ + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send; +} + +#[async_trait] +pub trait McBufVarintReadable +where + Self: Sized, +{ + async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send; +} + +#[async_trait] +impl McBufReadable for i32 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_int().await + } +} + +#[async_trait] +impl McBufVarintReadable for i32 { + async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_varint().await + } +} + +#[async_trait] +impl McBufReadable for Vec<u8> { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_bytes().await + } +} + +// string +#[async_trait] +impl McBufReadable for String { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_utf().await + } +} + +// ResourceLocation +#[async_trait] +impl McBufReadable for ResourceLocation { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_resource_location().await + } +} + +// u32 +#[async_trait] +impl McBufReadable for u32 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_int().await.map(|i| i as u32) + } +} + +// u32 varint +#[async_trait] +impl McBufVarintReadable for u32 { + async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_varint().await.map(|i| i as u32) + } +} + +// u16 +#[async_trait] +impl McBufReadable for u16 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_short().await.map(|i| i as u16) + } +} + +// u16 varint +#[async_trait] +impl McBufVarintReadable for u16 { + async fn varint_read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_varint().await.map(|i| i as u16) + } +} + +// i64 +#[async_trait] +impl McBufReadable for i64 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_long().await + } +} + +// u64 +#[async_trait] +impl McBufReadable for u64 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + i64::read_into(buf).await.map(|i| i as u64) + } +} + +// bool +#[async_trait] +impl McBufReadable for bool { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_boolean().await + } +} + +// u8 +#[async_trait] +impl McBufReadable for u8 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_byte().await + } +} + +// i8 +#[async_trait] +impl McBufReadable for i8 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_byte().await.map(|i| i as i8) + } +} + +// f32 +#[async_trait] +impl McBufReadable for f32 { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_float().await + } +} + +// GameType +#[async_trait] +impl McBufReadable for GameType { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + GameType::from_id(buf.read_byte().await?) + } +} + +// Option<GameType> +#[async_trait] +impl McBufReadable for Option<GameType> { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + GameType::from_optional_id(buf.read_byte().await? as i8) + } +} + +// Vec<ResourceLocation> +#[async_trait] +impl McBufReadable for Vec<ResourceLocation> { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let mut vec = Vec::new(); + let length = buf.read_varint().await?; + for _ in 0..length { + vec.push(buf.read_resource_location().await?); + } + Ok(vec) + } +} + +// azalea_nbt::Tag +#[async_trait] +impl McBufReadable for azalea_nbt::Tag { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + buf.read_nbt().await + } +} + +// Difficulty +#[async_trait] +impl McBufReadable for Difficulty { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + Ok(Difficulty::by_id(u8::read_into(buf).await?)) + } +} + +// Component +#[async_trait] +impl McBufReadable for Component { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let string = buf.read_utf().await?; + let json: serde_json::Value = serde_json::from_str(string.as_str()) + .map_err(|e| "Component isn't valid JSON".to_string())?; + let component = Component::deserialize(json).map_err(|e| e.to_string())?; + Ok(component) + } +} diff --git a/azalea-protocol/src/mc_buf/write.rs b/azalea-protocol/src/mc_buf/write.rs new file mode 100755 index 00000000..05f613d8 --- /dev/null +++ b/azalea-protocol/src/mc_buf/write.rs @@ -0,0 +1,341 @@ +use async_trait::async_trait; +use azalea_chat::component::Component; +use azalea_core::{ + difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation, +}; +use byteorder::{BigEndian, WriteBytesExt}; +use std::io::Write; + +use super::MAX_STRING_LENGTH; + +#[async_trait] +pub trait Writable { + fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> + where + F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, + T: Sized, + Self: Sized; + fn write_int_id_list(&mut self, list: &Vec<i32>) -> Result<(), std::io::Error>; + fn write_map<KF, VF, KT, VT>( + &mut self, + map: Vec<(KT, VT)>, + key_writer: KF, + value_writer: VF, + ) -> Result<(), std::io::Error> + where + KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, + VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, + Self: Sized; + + fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error>; + fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; + fn write_varint(&mut self, value: i32) -> Result<(), std::io::Error>; + fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error>; + fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error>; + fn write_short(&mut self, n: i16) -> Result<(), std::io::Error>; + fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>; + fn write_int(&mut self, n: i32) -> Result<(), std::io::Error>; + fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error>; + fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error>; + fn write_long(&mut self, n: i64) -> Result<(), std::io::Error>; + fn write_resource_location( + &mut self, + location: &ResourceLocation, + ) -> Result<(), std::io::Error>; + fn write_float(&mut self, n: f32) -> Result<(), std::io::Error>; +} + +#[async_trait] +impl Writable for Vec<u8> { + fn write_list<F, T>(&mut self, list: &[T], writer: F) -> Result<(), std::io::Error> + where + F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, + Self: Sized, + { + self.write_varint(list.len() as i32)?; + for item in list { + writer(self, item)?; + } + Ok(()) + } + + fn write_int_id_list(&mut self, list: &Vec<i32>) -> Result<(), std::io::Error> { + self.write_list(&list, |buf, n| buf.write_varint(*n)) + } + + fn write_map<KF, VF, KT, VT>( + &mut self, + map: Vec<(KT, VT)>, + key_writer: KF, + value_writer: VF, + ) -> Result<(), std::io::Error> + where + KF: Fn(&mut Self, KT) -> Result<(), std::io::Error> + Copy, + VF: Fn(&mut Self, VT) -> Result<(), std::io::Error> + Copy, + Self: Sized, + { + self.write_varint(map.len() as i32)?; + for (key, value) in map { + key_writer(self, key)?; + value_writer(self, value)?; + } + Ok(()) + } + + fn write_byte(&mut self, n: u8) -> Result<(), std::io::Error> { + WriteBytesExt::write_u8(self, n) + } + + fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { + self.extend_from_slice(bytes); + Ok(()) + } + + fn write_varint(&mut self, mut value: i32) -> Result<(), std::io::Error> { + let mut buffer = [0]; + if value == 0 { + self.write_all(&buffer).unwrap(); + } + while value != 0 { + buffer[0] = (value & 0b0111_1111) as u8; + value = (value >> 7) & (i32::max_value() >> 6); + if value != 0 { + buffer[0] |= 0b1000_0000; + } + self.write_all(&buffer)?; + } + Ok(()) + } + + fn write_utf_with_len(&mut self, string: &str, len: usize) -> Result<(), std::io::Error> { + if string.len() > len { + panic!( + "String too big (was {} bytes encoded, max {})", + string.len(), + len + ); + } + self.write_varint(string.len() as i32)?; + self.write_bytes(string.as_bytes()) + } + + fn write_utf(&mut self, string: &str) -> Result<(), std::io::Error> { + self.write_utf_with_len(string, MAX_STRING_LENGTH.into()) + } + + fn write_short(&mut self, n: i16) -> Result<(), std::io::Error> { + WriteBytesExt::write_i16::<BigEndian>(self, n) + } + + fn write_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error> { + self.write_varint(bytes.len() as i32)?; + self.write_bytes(bytes) + } + + fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> { + WriteBytesExt::write_i32::<BigEndian>(self, n) + } + + fn write_boolean(&mut self, b: bool) -> Result<(), std::io::Error> { + self.write_byte(if b { 1 } else { 0 }) + } + + fn write_nbt(&mut self, nbt: &azalea_nbt::Tag) -> Result<(), std::io::Error> { + nbt.write(self) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) + } + + fn write_long(&mut self, n: i64) -> Result<(), std::io::Error> { + WriteBytesExt::write_i64::<BigEndian>(self, n) + } + + fn write_float(&mut self, n: f32) -> Result<(), std::io::Error> { + WriteBytesExt::write_f32::<BigEndian>(self, n) + } + + fn write_resource_location( + &mut self, + location: &ResourceLocation, + ) -> Result<(), std::io::Error> { + self.write_utf(&location.to_string()) + } +} + +pub trait McBufWritable +where + Self: Sized, +{ + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>; +} + +pub trait McBufVarintWritable +where + Self: Sized, +{ + fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>; +} + +impl McBufWritable for i32 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + Writable::write_int(buf, *self) + } +} + +impl McBufVarintWritable for i32 { + fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_varint(*self) + } +} + +impl McBufWritable for Vec<u8> { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_bytes(self) + } +} + +// string +impl McBufWritable for String { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_utf(self) + } +} + +// ResourceLocation +impl McBufWritable for ResourceLocation { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_resource_location(self) + } +} + +// u32 +impl McBufWritable for u32 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + i16::write_into(&(*self as i16), buf) + } +} + +// u32 varint +impl McBufVarintWritable for u32 { + fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + i32::varint_write_into(&(*self as i32), buf) + } +} + +// u16 +impl McBufWritable for u16 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + i16::write_into(&(*self as i16), buf) + } +} + +// u16 varint +impl McBufVarintWritable for u16 { + fn varint_write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + i32::varint_write_into(&(*self as i32), buf) + } +} + +// u8 +impl McBufWritable for u8 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_byte(*self) + } +} + +// i16 +impl McBufWritable for i16 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + Writable::write_short(buf, *self) + } +} + +// i64 +impl McBufWritable for i64 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + Writable::write_long(buf, *self) + } +} + +// u64 +impl McBufWritable for u64 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + i64::write_into(&(*self as i64), buf) + } +} + +// bool +impl McBufWritable for bool { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_boolean(*self) + } +} + +// i8 +impl McBufWritable for i8 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_byte(*self as u8) + } +} + +// f32 +impl McBufWritable for f32 { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_float(*self) + } +} + +// GameType +impl McBufWritable for GameType { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + u8::write_into(&self.to_id(), buf) + } +} + +// Option<GameType> +impl McBufWritable for Option<GameType> { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_byte(GameType::to_optional_id(self) as u8) + } +} + +// Vec<ResourceLocation> +impl McBufWritable for Vec<ResourceLocation> { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_list(self, |buf, resource_location| { + buf.write_resource_location(resource_location) + }) + } +} + +// azalea_nbt::Tag +impl McBufWritable for azalea_nbt::Tag { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_nbt(self) + } +} + +// Difficulty +impl McBufWritable for Difficulty { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + u8::write_into(&self.id(), buf) + } +} + +// Component +#[async_trait] +impl McBufWritable for Component { + // async fn read_into<R>(buf: &mut R) -> Result<Self, String> + // where + // R: AsyncRead + std::marker::Unpin + std::marker::Send, + // { + // let string = buf.read_utf().await?; + // let json: serde_json::Value = serde_json::from_str(string.as_str()) + // .map_err(|e| "Component isn't valid JSON".to_string())?; + // let component = Component::deserialize(json).map_err(|e| e.to_string())?; + // Ok(component) + // } + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + // component doesn't have serialize implemented yet + todo!() + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs new file mode 100755 index 00000000..e12cfff3 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_change_difficulty_packet.rs @@ -0,0 +1,8 @@ +use azalea_core::difficulty::Difficulty; +use packet_macros::GamePacket; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundChangeDifficultyPacket { + pub difficulty: Difficulty, + pub locked: bool, +} diff --git a/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs new file mode 100755 index 00000000..134a3109 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_custom_payload_packet.rs @@ -0,0 +1,8 @@ +use azalea_core::resource_location::ResourceLocation; +use packet_macros::GamePacket; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundCustomPayloadPacket { + pub identifier: ResourceLocation, + pub data: Vec<u8>, +} diff --git a/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs new file mode 100755 index 00000000..1403630d --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_declare_commands_packet.rs @@ -0,0 +1,96 @@ +use std::hash::Hash; + +use async_trait::async_trait; +use tokio::io::AsyncRead; + +use crate::mc_buf::{McBufReadable, Readable}; + +use super::GamePacket; + +#[derive(Hash, Clone, Debug)] +pub struct ClientboundDeclareCommandsPacket { + pub entries: Vec<BrigadierNodeStub>, + pub root_index: i32, +} + +impl ClientboundDeclareCommandsPacket { + pub fn get(self) -> GamePacket { + GamePacket::ClientboundDeclareCommandsPacket(self) + } + + pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + panic!("ClientboundDeclareCommandsPacket::write not implemented") + } + + pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( + buf: &mut T, + ) -> Result<GamePacket, String> { + let node_count = buf.read_varint().await?; + println!("node_count: {}", node_count); + let mut nodes = Vec::with_capacity(node_count as usize); + for _ in 0..node_count { + let node = BrigadierNodeStub::read_into(buf).await?; + nodes.push(node); + } + let root_index = buf.read_varint().await?; + Ok(GamePacket::ClientboundDeclareCommandsPacket( + ClientboundDeclareCommandsPacket { + entries: nodes, + root_index, + }, + )) + } +} + +#[derive(Hash, Debug, Clone)] +pub struct BrigadierNodeStub {} + +// azalea_brigadier::tree::CommandNode +#[async_trait] +impl McBufReadable for BrigadierNodeStub { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let flags = u8::read_into(buf).await?; + + let node_type = flags & 0x03; + let is_executable = flags & 0x04 != 0; + let has_redirect = flags & 0x08 != 0; + let has_suggestions_type = flags & 0x10 != 0; + println!("flags: {}, node_type: {}, is_executable: {}, has_redirect: {}, has_suggestions_type: {}", flags, node_type, is_executable, has_redirect, has_suggestions_type); + + let children = buf.read_int_id_list().await?; + println!("children: {:?}", children); + let redirect_node = if has_redirect { + buf.read_varint().await? + } else { + 0 + }; + println!("redirect_node: {}", redirect_node); + + if node_type == 2 { + let name = buf.read_utf().await?; + println!("name: {}", name); + + let resource_location = if has_suggestions_type { + Some(buf.read_resource_location().await?) + } else { + None + }; + println!( + "node_type=2, flags={}, name={}, resource_location={:?}", + flags, name, resource_location + ); + return Ok(BrigadierNodeStub {}); + } + if node_type == 1 { + let name = buf.read_utf().await?; + println!("node_type=1, flags={}, name={}", flags, name); + return Ok(BrigadierNodeStub {}); + } + println!("node_type={}, flags={}", node_type, flags); + Ok(BrigadierNodeStub {}) + // return Err("Unknown node type".to_string()); + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs new file mode 100644 index 00000000..74f5f72e --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_disconnect_packet.rs @@ -0,0 +1,9 @@ +use azalea_chat::component::Component; +use azalea_core::resource_location::ResourceLocation; +use packet_macros::GamePacket; +use serde::Deserialize; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundDisconnectPacket { + pub reason: Component, +} diff --git a/azalea-protocol/src/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs index 9043fa1a..57869202 100644..100755 --- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs @@ -1,26 +1,8 @@ -use super::GamePacket; -use crate::mc_buf::{Readable, Writable}; use azalea_core::{game_type::GameType, resource_location::ResourceLocation}; +use packet_macros::GamePacket; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, GamePacket)] pub struct ClientboundLoginPacket { - // private final int playerId; - // private final boolean hardcore; - // private final GameType gameType; - // @Nullable - // private final GameType previousGameType; - // private final Set<ResourceKey<Level>> levels; - // private final RegistryAccess.RegistryHolder registryHolder; - // private final DimensionType dimensionType; - // private final ResourceKey<Level> dimension; - // private final long seed; - // private final int maxPlayers; - // private final int chunkRadius; - // private final int simulationDistance; - // private final boolean reducedDebugInfo; - // private final boolean showDeathScreen; - // private final boolean isDebug; - // private final boolean isFlat; pub player_id: i32, pub hardcore: bool, pub game_type: GameType, @@ -30,93 +12,14 @@ pub struct ClientboundLoginPacket { pub dimension_type: azalea_nbt::Tag, pub dimension: ResourceLocation, pub seed: i64, + #[varint] pub max_players: i32, + #[varint] pub chunk_radius: i32, + #[varint] pub simulation_distance: i32, pub reduced_debug_info: bool, pub show_death_screen: bool, pub is_debug: bool, pub is_flat: bool, } - -impl ClientboundLoginPacket { - pub fn get(self) -> GamePacket { - GamePacket::ClientboundLoginPacket(self) - } - - pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { - buf.write_int(self.player_id)?; - buf.write_boolean(self.hardcore)?; - buf.write_byte(self.game_type.to_id())?; - buf.write_byte(GameType::to_optional_id(&self.previous_game_type) as u8)?; - buf.write_list(&self.levels, |buf, resource_location| { - buf.write_resource_location(resource_location) - })?; - buf.write_nbt(&self.registry_holder)?; - buf.write_nbt(&self.dimension_type)?; - buf.write_resource_location(&self.dimension)?; - buf.write_long(self.seed)?; - buf.write_varint(self.max_players)?; - buf.write_varint(self.chunk_radius)?; - buf.write_varint(self.simulation_distance)?; - buf.write_boolean(self.reduced_debug_info)?; - buf.write_boolean(self.show_death_screen)?; - buf.write_boolean(self.is_debug)?; - buf.write_boolean(self.is_flat)?; - Ok(()) - } - - pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - buf: &mut T, - ) -> Result<GamePacket, String> { - let player_id = buf.read_int().await?; - let hardcore = buf.read_boolean().await?; - let game_type = GameType::from_id(buf.read_byte().await?)?; - let previous_game_type = GameType::from_optional_id(buf.read_byte().await? as i8)?; - - let mut levels = Vec::new(); - let length = buf.read_varint().await?; - for _ in 0..length { - levels.push(buf.read_resource_location().await?); - } - - // println!("about to read nbt"); - // // read all the bytes into a buffer, print it, and panic - // let mut registry_holder_buf = Vec::new(); - // buf.read_to_end(&mut registry_holder_buf).await.unwrap(); - // println!("{:?}", String::from_utf8_lossy(®istry_holder_buf)); - // panic!(""); - - let registry_holder = buf.read_nbt().await?; - let dimension_type = buf.read_nbt().await?; - let dimension = buf.read_resource_location().await?; - let seed = buf.read_long().await?; - let max_players = buf.read_varint().await?; - let chunk_radius = buf.read_varint().await?; - let simulation_distance = buf.read_varint().await?; - let reduced_debug_info = buf.read_boolean().await?; - let show_death_screen = buf.read_boolean().await?; - let is_debug = buf.read_boolean().await?; - let is_flat = buf.read_boolean().await?; - - Ok(ClientboundLoginPacket { - player_id, - hardcore, - game_type, - previous_game_type, - levels, - registry_holder, - dimension_type, - dimension, - seed, - max_players, - chunk_radius, - simulation_distance, - reduced_debug_info, - show_death_screen, - is_debug, - is_flat, - } - .get()) - } -} diff --git a/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs new file mode 100755 index 00000000..f4f528cf --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_player_abilities_packet.rs @@ -0,0 +1,57 @@ +// i don't know the actual name of this packet, i couldn't find it in the source code + +use crate::mc_buf::{McBufReadable, McBufWritable, Readable}; +use async_trait::async_trait; +use packet_macros::GamePacket; +use tokio::io::AsyncRead; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundPlayerAbilitiesPacket { + pub flags: PlayerAbilitiesFlags, + pub flying_speed: f32, + /// Used for the fov + pub walking_speed: f32, +} + +#[derive(Clone, Debug)] +pub struct PlayerAbilitiesFlags { + pub invulnerable: bool, + pub flying: bool, + pub can_fly: bool, + pub instant_break: bool, +} + +#[async_trait] +impl McBufReadable for PlayerAbilitiesFlags { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let byte = buf.read_byte().await?; + Ok(PlayerAbilitiesFlags { + invulnerable: byte & 1 != 0, + flying: byte & 2 != 0, + can_fly: byte & 4 != 0, + instant_break: byte & 8 != 0, + }) + } +} + +impl McBufWritable for PlayerAbilitiesFlags { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + let mut byte = 0; + if self.invulnerable { + byte = byte | 1; + } + if self.flying { + byte = byte | 2; + } + if self.can_fly { + byte = byte | 4; + } + if self.instant_break { + byte = byte | 8; + } + u8::write_into(&byte, buf) + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs new file mode 100755 index 00000000..4f0bf575 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_set_carried_item_packet.rs @@ -0,0 +1,7 @@ +use packet_macros::GamePacket; + +/// Sent to change the player's slot selection. +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundSetCarriedItemPacket { + pub slot: u8, +} diff --git a/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs new file mode 100755 index 00000000..a2f17370 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_update_tags_packet.rs @@ -0,0 +1,105 @@ +use std::collections::HashMap; + +use async_trait::async_trait; +use azalea_core::resource_location::ResourceLocation; +use packet_macros::GamePacket; +use tokio::io::AsyncRead; + +use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable}; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundUpdateTagsPacket { + pub tags: HashMap<ResourceLocation, Vec<Tags>>, +} + +#[derive(Clone, Debug)] +pub struct Tags { + pub name: ResourceLocation, + pub elements: Vec<i32>, +} + +#[async_trait] +impl McBufReadable for HashMap<ResourceLocation, Vec<Tags>> { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + println!("reading tags!"); + let length = buf.read_varint().await? as usize; + println!("length: {}", length); + let mut data = HashMap::with_capacity(length); + for _ in 0..length { + let tag_type = buf.read_resource_location().await?; + println!("read tag type {}", tag_type); + let tags_count = buf.read_varint().await? as usize; + let mut tags_vec = Vec::with_capacity(tags_count); + for _ in 0..tags_count { + let tags = Tags::read_into(buf).await?; + tags_vec.push(tags); + } + println!("tags: {} {:?}", tag_type, tags_vec); + data.insert(tag_type, tags_vec); + } + Ok(data) + } +} + +impl McBufWritable for HashMap<ResourceLocation, Vec<Tags>> { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_varint(self.len() as i32)?; + for (k, v) in self { + k.write_into(buf)?; + v.write_into(buf)?; + } + Ok(()) + } +} + +#[async_trait] +impl McBufReadable for Vec<Tags> { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + let tags_count = buf.read_varint().await? as usize; + let mut tags_vec = Vec::with_capacity(tags_count); + for _ in 0..tags_count { + let tags = Tags::read_into(buf).await?; + tags_vec.push(tags); + } + Ok(tags_vec) + } +} + +impl McBufWritable for Vec<Tags> { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_varint(self.len() as i32)?; + for tag in self { + tag.write_into(buf)?; + } + Ok(()) + } +} + +#[async_trait] +impl McBufReadable for Tags { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + println!("reading tags."); + let name = buf.read_resource_location().await?; + println!("tags name: {}", name); + let elements = buf.read_int_id_list().await?; + println!("elements: {:?}", elements); + Ok(Tags { name, elements }) + } +} + +impl McBufWritable for Tags { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + self.name.write_into(buf)?; + buf.write_int_id_list(&self.elements)?; + Ok(()) + } +} diff --git a/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs new file mode 100755 index 00000000..8301c089 --- /dev/null +++ b/azalea-protocol/src/packets/game/clientbound_update_view_distance_packet.rs @@ -0,0 +1,9 @@ +// i don't know the actual name of this packet, i couldn't find it in the source code + +use packet_macros::GamePacket; + +#[derive(Clone, Debug, GamePacket)] +pub struct ClientboundUpdateViewDistancePacket { + #[varint] + pub view_distance: i32, +} diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs index 00fa1d75..dde3f753 100644..100755 --- a/azalea-protocol/src/packets/game/mod.rs +++ b/azalea-protocol/src/packets/game/mod.rs @@ -1,46 +1,27 @@ +pub mod clientbound_change_difficulty_packet; +pub mod clientbound_custom_payload_packet; +pub mod clientbound_declare_commands_packet; +pub mod clientbound_disconnect_packet; pub mod clientbound_login_packet; +pub mod clientbound_player_abilities_packet; +pub mod clientbound_set_carried_item_packet; +pub mod clientbound_update_tags_packet; +pub mod clientbound_update_view_distance_packet; -use super::ProtocolPacket; -use crate::connect::PacketFlow; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -#[derive(Clone, Debug)] -pub enum GamePacket -where - Self: Sized, -{ - ClientboundLoginPacket(clientbound_login_packet::ClientboundLoginPacket), -} - -#[async_trait] -impl ProtocolPacket for GamePacket { - fn id(&self) -> u32 { - match self { - GamePacket::ClientboundLoginPacket(_packet) => 0x00, - } - } - - fn write(&self, _buf: &mut Vec<u8>) {} - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result<GamePacket, String> - where - Self: Sized, - { - Ok(match flow { - PacketFlow::ServerToClient => match id { - 0x26 => clientbound_login_packet::ClientboundLoginPacket::read(buf).await?, - - _ => return Err(format!("Unknown ServerToClient game packet id: {}", id)), - }, - PacketFlow::ClientToServer => match id { - // 0x00 => serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?, - _ => return Err(format!("Unknown ClientToServer game packet id: {}", id)), - }, - }) +declare_state_packets!( + GamePacket, + Serverbound => {}, + Clientbound => { + 0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket, + 0x12: clientbound_declare_commands_packet::ClientboundDeclareCommandsPacket, + 0x1a: clientbound_disconnect_packet::ClientboundDisconnectPacket, + 0x18: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket, + 0x26: clientbound_login_packet::ClientboundLoginPacket, + 0x32: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket, + 0x48: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket, + 0x4a: clientbound_update_view_distance_packet::ClientboundUpdateViewDistancePacket, + 0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket } -} +); diff --git a/azalea-protocol/src/packets/handshake/client_intention_packet.rs b/azalea-protocol/src/packets/handshake/client_intention_packet.rs index 939a695e..a92d65f6 100644..100755 --- a/azalea-protocol/src/packets/handshake/client_intention_packet.rs +++ b/azalea-protocol/src/packets/handshake/client_intention_packet.rs @@ -1,34 +1,34 @@ -use std::hash::Hash; - use crate::{mc_buf::Writable, packets::ConnectionProtocol}; +use packet_macros::HandshakePacket; +use std::hash::Hash; use super::HandshakePacket; -#[derive(Hash, Clone, Debug)] +#[derive(Hash, Clone, Debug, HandshakePacket)] pub struct ClientIntentionPacket { + #[varint] pub protocol_version: u32, pub hostname: String, pub port: u16, - /// 1 for status, 2 for login pub intention: ConnectionProtocol, } -impl ClientIntentionPacket { - pub fn get(self) -> HandshakePacket { - HandshakePacket::ClientIntentionPacket(self) - } +// impl ClientIntentionPacket { +// pub fn get(self) -> HandshakePacket { +// HandshakePacket::ClientIntentionPacket(self) +// } - pub fn write(&self, buf: &mut Vec<u8>) { - buf.write_varint(self.protocol_version as i32).unwrap(); - buf.write_utf(&self.hostname).unwrap(); - buf.write_short(self.port).unwrap(); - buf.write_varint(self.intention.clone() as i32).unwrap(); - } +// pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { +// buf.write_varint(self.protocol_version as i32)?; +// buf.write_utf(&self.hostname)?; +// buf.write_short(self.port as i16)?; +// buf.write_varint(self.intention as i32)?; +// Ok(()) +// } - pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - _buf: &mut T, - ) -> Result<HandshakePacket, String> { - Err("ClientIntentionPacket::parse not implemented".to_string()) - // Ok(ClientIntentionPacket {}.get()) - } -} +// pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( +// buf: &mut T, +// ) -> Result<HandshakePacket, String> { +// todo!() +// } +// } diff --git a/azalea-protocol/src/packets/handshake/mod.rs b/azalea-protocol/src/packets/handshake/mod.rs index 1d753645..88d9939b 100644..100755 --- a/azalea-protocol/src/packets/handshake/mod.rs +++ b/azalea-protocol/src/packets/handshake/mod.rs @@ -1,48 +1,11 @@ pub mod client_intention_packet; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -use crate::connect::PacketFlow; - -use super::ProtocolPacket; - -#[derive(Clone, Debug)] -pub enum HandshakePacket -where - Self: Sized, -{ - ClientIntentionPacket(client_intention_packet::ClientIntentionPacket), -} - -#[async_trait] -impl ProtocolPacket for HandshakePacket { - fn id(&self) -> u32 { - match self { - HandshakePacket::ClientIntentionPacket(_packet) => 0x00, - } - } - - fn write(&self, buf: &mut Vec<u8>) { - match self { - HandshakePacket::ClientIntentionPacket(packet) => packet.write(buf), - } - } - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result<HandshakePacket, String> - where - Self: Sized, - { - match flow { - PacketFlow::ServerToClient => Err("HandshakePacket::read not implemented".to_string()), - PacketFlow::ClientToServer => match id { - 0x00 => Ok(client_intention_packet::ClientIntentionPacket::read(buf).await?), - _ => Err(format!("Unknown ClientToServer status packet id: {}", id)), - }, - } +declare_state_packets!( + HandshakePacket, + Serverbound => {}, + Clientbound => { + 0x00: client_intention_packet::ClientIntentionPacket, } -} +); diff --git a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs index 2bc1fc1e..3138106e 100644..100755 --- a/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_custom_query_packet.rs @@ -1,37 +1,11 @@ -use super::LoginPacket; -use crate::mc_buf::{Readable, Writable}; use azalea_core::resource_location::ResourceLocation; +use packet_macros::LoginPacket; use std::hash::Hash; -#[derive(Hash, Clone, Debug)] +#[derive(Hash, Clone, Debug, LoginPacket)] pub struct ClientboundCustomQueryPacket { + #[varint] pub transaction_id: u32, pub identifier: ResourceLocation, pub data: Vec<u8>, } - -impl ClientboundCustomQueryPacket { - pub fn get(self) -> LoginPacket { - LoginPacket::ClientboundCustomQueryPacket(self) - } - - pub fn write(&self, buf: &mut Vec<u8>) { - buf.write_varint(self.transaction_id as i32).unwrap(); - buf.write_utf(self.identifier.to_string().as_str()).unwrap(); - buf.write_bytes(&self.data).unwrap(); - } - - pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - buf: &mut T, - ) -> Result<LoginPacket, String> { - let transaction_id = buf.read_varint().await? as u32; - let identifier = ResourceLocation::new(&buf.read_utf().await?)?; - let data = buf.read_bytes(1048576).await?; - Ok(ClientboundCustomQueryPacket { - transaction_id, - identifier, - data, - } - .get()) - } -} diff --git a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs index 1a752c1a..c54aa819 100644..100755 --- a/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_game_profile_packet.rs @@ -14,11 +14,12 @@ impl ClientboundGameProfilePacket { LoginPacket::ClientboundGameProfilePacket(self) } - pub fn write(&self, buf: &mut Vec<u8>) { + pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { for n in self.game_profile.uuid.to_int_array() { buf.write_int(n as i32).unwrap(); } buf.write_utf(self.game_profile.name.as_str()).unwrap(); + Ok(()) } pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( diff --git a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs index e0b865be..9d0cec39 100644..100755 --- a/azalea-protocol/src/packets/login/clientbound_hello_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_hello_packet.rs @@ -16,7 +16,7 @@ impl ClientboundHelloPacket { LoginPacket::ClientboundHelloPacket(self) } - pub fn write(&self, _buf: &mut Vec<u8>) { + pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> { panic!("ClientboundHelloPacket::write not implemented") } diff --git a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs index af355192..a88c6cbf 100644..100755 --- a/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs +++ b/azalea-protocol/src/packets/login/clientbound_login_compression_packet.rs @@ -14,8 +14,9 @@ impl ClientboundLoginCompressionPacket { LoginPacket::ClientboundLoginCompressionPacket(self) } - pub fn write(&self, buf: &mut Vec<u8>) { + pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { buf.write_varint(self.compression_threshold).unwrap(); + Ok(()) } pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( diff --git a/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs new file mode 100644 index 00000000..28d91c79 --- /dev/null +++ b/azalea-protocol/src/packets/login/clientbound_login_disconnect_packet.rs @@ -0,0 +1,7 @@ +use azalea_chat::component::Component; +use packet_macros::LoginPacket; + +#[derive(Clone, Debug, LoginPacket)] +pub struct ClientboundLoginDisconnectPacket { + pub reason: Component, +} diff --git a/azalea-protocol/src/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs index 4d490d08..ab68518c 100644..100755 --- a/azalea-protocol/src/packets/login/mod.rs +++ b/azalea-protocol/src/packets/login/mod.rs @@ -2,78 +2,22 @@ pub mod clientbound_custom_query_packet; pub mod clientbound_game_profile_packet; pub mod clientbound_hello_packet; pub mod clientbound_login_compression_packet; +pub mod clientbound_login_disconnect_packet; pub mod serverbound_hello_packet; -use super::ProtocolPacket; -use crate::connect::PacketFlow; -use async_trait::async_trait; +use packet_macros::declare_state_packets; -#[derive(Clone, Debug)] -pub enum LoginPacket -where - Self: Sized, -{ - ClientboundCustomQueryPacket(clientbound_custom_query_packet::ClientboundCustomQueryPacket), - ClientboundGameProfilePacket(clientbound_game_profile_packet::ClientboundGameProfilePacket), - ClientboundHelloPacket(clientbound_hello_packet::ClientboundHelloPacket), - ClientboundLoginCompressionPacket( - clientbound_login_compression_packet::ClientboundLoginCompressionPacket, - ), - ServerboundHelloPacket(serverbound_hello_packet::ServerboundHelloPacket), -} - -#[async_trait] -impl ProtocolPacket for LoginPacket { - fn id(&self) -> u32 { - match self { - LoginPacket::ClientboundCustomQueryPacket(_packet) => 0x04, - LoginPacket::ClientboundGameProfilePacket(_packet) => 0x02, - LoginPacket::ClientboundHelloPacket(_packet) => 0x01, - LoginPacket::ClientboundLoginCompressionPacket(_packet) => 0x03, - LoginPacket::ServerboundHelloPacket(_packet) => 0x00, - } - } - - fn write(&self, buf: &mut Vec<u8>) { - match self { - LoginPacket::ClientboundCustomQueryPacket(packet) => packet.write(buf), - LoginPacket::ClientboundGameProfilePacket(packet) => packet.write(buf), - LoginPacket::ClientboundHelloPacket(packet) => packet.write(buf), - LoginPacket::ClientboundLoginCompressionPacket(packet) => packet.write(buf), - LoginPacket::ServerboundHelloPacket(packet) => packet.write(buf), - } - } - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result<LoginPacket, String> - where - Self: Sized, - { - Ok(match flow { - PacketFlow::ServerToClient => match id { - 0x01 => clientbound_hello_packet::ClientboundHelloPacket::read(buf).await?, - 0x02 => { - clientbound_game_profile_packet::ClientboundGameProfilePacket::read(buf).await? - } - 0x04 => { - clientbound_custom_query_packet::ClientboundCustomQueryPacket::read(buf).await? - } - 0x03 => { - clientbound_login_compression_packet::ClientboundLoginCompressionPacket::read( - buf, - ) - .await? - } - _ => return Err(format!("Unknown ServerToClient login packet id: {}", id)), - }, - PacketFlow::ClientToServer => match id { - 0x00 => serverbound_hello_packet::ServerboundHelloPacket::read(buf).await?, - _ => return Err(format!("Unknown ClientToServer login packet id: {}", id)), - }, - }) +declare_state_packets!( + LoginPacket, + Serverbound => { + 0x00: serverbound_hello_packet::ServerboundHelloPacket, + }, + Clientbound => { + // 0x00: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + 26: clientbound_login_disconnect_packet::ClientboundLoginDisconnectPacket, + 0x01: clientbound_hello_packet::ClientboundHelloPacket, + 0x02: clientbound_game_profile_packet::ClientboundGameProfilePacket, + 0x03: clientbound_login_compression_packet::ClientboundLoginCompressionPacket, + 0x04: clientbound_custom_query_packet::ClientboundCustomQueryPacket, } -} +); diff --git a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs index 0039cbce..a72480f2 100644..100755 --- a/azalea-protocol/src/packets/login/serverbound_hello_packet.rs +++ b/azalea-protocol/src/packets/login/serverbound_hello_packet.rs @@ -14,8 +14,9 @@ impl ServerboundHelloPacket { LoginPacket::ServerboundHelloPacket(self) } - pub fn write(&self, buf: &mut Vec<u8>) { + pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { buf.write_utf(&self.username).unwrap(); + Ok(()) } pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( diff --git a/azalea-protocol/src/packets/mod.rs b/azalea-protocol/src/packets/mod.rs index e065b65c..98741a75 100644..100755 --- a/azalea-protocol/src/packets/mod.rs +++ b/azalea-protocol/src/packets/mod.rs @@ -3,13 +3,18 @@ pub mod handshake; pub mod login; pub mod status; +use crate::{ + connect::PacketFlow, + mc_buf::{McBufReadable, McBufWritable, Readable, Writable}, +}; use async_trait::async_trait; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use tokio::io::AsyncRead; -use crate::connect::PacketFlow; +pub const PROTOCOL_VERSION: u32 = 758; -pub const PROTOCOL_VERSION: u32 = 757; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive)] pub enum ConnectionProtocol { Handshake = -1, Game = 0, @@ -42,5 +47,22 @@ where where Self: Sized; - fn write(&self, buf: &mut Vec<u8>); + fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error>; +} + +#[async_trait] +impl McBufReadable for ConnectionProtocol { + async fn read_into<R>(buf: &mut R) -> Result<Self, String> + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { + ConnectionProtocol::from_i32(buf.read_varint().await?) + .ok_or_else(|| "Invalid intention".to_string()) + } +} + +impl McBufWritable for ConnectionProtocol { + fn write_into(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + buf.write_varint(*self as i32) + } } diff --git a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs index 38270ad1..884cf408 100644..100755 --- a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs +++ b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs @@ -36,10 +36,12 @@ pub struct ClientboundStatusResponsePacket { impl ClientboundStatusResponsePacket { pub fn get(self) -> StatusPacket { - StatusPacket::ClientboundStatusResponsePacket(Box::new(self)) + StatusPacket::ClientboundStatusResponsePacket(self) } - pub fn write(&self, _buf: &mut Vec<u8>) {} + pub fn write(&self, _buf: &mut Vec<u8>) -> Result<(), std::io::Error> { + Ok(()) + } pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( buf: &mut T, diff --git a/azalea-protocol/src/packets/status/mod.rs b/azalea-protocol/src/packets/status/mod.rs index 6383bae8..56aa577e 100644..100755 --- a/azalea-protocol/src/packets/status/mod.rs +++ b/azalea-protocol/src/packets/status/mod.rs @@ -1,65 +1,14 @@ pub mod clientbound_status_response_packet; pub mod serverbound_status_request_packet; -use async_trait::async_trait; - -use crate::connect::PacketFlow; - -use super::ProtocolPacket; - -#[derive(Clone, Debug)] -pub enum StatusPacket -where - Self: Sized, -{ - ServerboundStatusRequestPacket( - serverbound_status_request_packet::ServerboundStatusRequestPacket, - ), - ClientboundStatusResponsePacket( - Box<clientbound_status_response_packet::ClientboundStatusResponsePacket>, - ), -} - -#[async_trait] -impl ProtocolPacket for StatusPacket { - fn id(&self) -> u32 { - match self { - StatusPacket::ServerboundStatusRequestPacket(_packet) => 0x00, - StatusPacket::ClientboundStatusResponsePacket(_packet) => 0x00, - } - } - - fn write(&self, buf: &mut Vec<u8>) { - match self { - StatusPacket::ServerboundStatusRequestPacket(packet) => packet.write(buf), - StatusPacket::ClientboundStatusResponsePacket(packet) => packet.write(buf), - } - } - - /// Read a packet by its id, ConnectionProtocol, and flow - async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - id: u32, - flow: &PacketFlow, - buf: &mut T, - ) -> Result<StatusPacket, String> - where - Self: Sized, - { - match flow { - PacketFlow::ServerToClient => match id { - 0x00 => Ok( - clientbound_status_response_packet::ClientboundStatusResponsePacket::read(buf) - .await?, - ), - _ => Err(format!("Unknown ServerToClient status packet id: {}", id)), - }, - PacketFlow::ClientToServer => match id { - 0x00 => Ok( - serverbound_status_request_packet::ServerboundStatusRequestPacket::read(buf) - .await?, - ), - _ => Err(format!("Unknown ClientToServer status packet id: {}", id)), - }, - } +use packet_macros::declare_state_packets; + +declare_state_packets!( + StatusPacket, + Serverbound => { + 0x00: serverbound_status_request_packet::ServerboundStatusRequestPacket, + }, + Clientbound => { + 0x00: clientbound_status_response_packet::ClientboundStatusResponsePacket, } -} +); diff --git a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs index 3a25ac42..e77687ec 100644..100755 --- a/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs +++ b/azalea-protocol/src/packets/status/serverbound_status_request_packet.rs @@ -1,22 +1,5 @@ +use packet_macros::StatusPacket; use std::hash::Hash; -use super::StatusPacket; - -#[derive(Hash, Clone, Debug)] +#[derive(Clone, Debug, StatusPacket)] pub struct ServerboundStatusRequestPacket {} - -impl ServerboundStatusRequestPacket { - pub fn get(self) -> StatusPacket { - StatusPacket::ServerboundStatusRequestPacket(self) - } - - pub fn write(&self, _buf: &mut Vec<u8>) { - panic!("ServerboundStatusRequestPacket::write not implemented") - } - - pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>( - _buf: &mut T, - ) -> Result<StatusPacket, String> { - Err("ServerboundStatusRequestPacket::read not implemented".to_string()) - } -} diff --git a/azalea-protocol/src/read.rs b/azalea-protocol/src/read.rs index ed36d6b6..ed36d6b6 100644..100755 --- a/azalea-protocol/src/read.rs +++ b/azalea-protocol/src/read.rs diff --git a/azalea-protocol/src/resolver.rs b/azalea-protocol/src/resolver.rs index 24687a6e..24687a6e 100644..100755 --- a/azalea-protocol/src/resolver.rs +++ b/azalea-protocol/src/resolver.rs diff --git a/azalea-protocol/src/write.rs b/azalea-protocol/src/write.rs index 3898e74a..821ed6e8 100644..100755 --- a/azalea-protocol/src/write.rs +++ b/azalea-protocol/src/write.rs @@ -17,7 +17,7 @@ fn packet_encoder<P: ProtocolPacket + std::fmt::Debug>(packet: &P) -> Result<Vec let mut buf = Vec::new(); buf.write_varint(packet.id() as i32) .map_err(|e| e.to_string())?; - packet.write(&mut buf); + packet.write(&mut buf).map_err(|e| e.to_string())?; if buf.len() > MAXIMUM_UNCOMPRESSED_LENGTH as usize { return Err(format!( "Packet too big (is {} bytes, should be less than {}): {:?}", |
