From af28b0e57aeeca8790e3014f3e568c60ae892e39 Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 26 Dec 2021 14:15:06 -0600 Subject: [PATCH] reading nbt in the protocol works --- azalea-client/src/connect.rs | 37 ++++---- azalea-core/src/game_type.rs | 15 ++- azalea-core/src/registries.rs | 8 -- azalea-core/src/resource_location.rs | 2 +- azalea-nbt/src/decode.rs | 13 ++- azalea-protocol/src/mc_buf.rs | 92 ++++++++++++++++++- .../packets/game/clientbound_login_packet.rs | 77 +++++++++++++--- azalea-protocol/src/packets/game/mod.rs | 19 ++-- azalea-protocol/src/packets/login/mod.rs | 4 +- 9 files changed, 211 insertions(+), 56 deletions(-) delete mode 100644 azalea-core/src/registries.rs diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs index d3702874..90cb3ad7 100644 --- a/azalea-client/src/connect.rs +++ b/azalea-client/src/connect.rs @@ -57,23 +57,26 @@ pub async fn join_server(address: &ServerAddress) -> Result<(), String> { }; // game - // loop { - // let packet_result = conn.read().await; - // match packet_result { - // Ok(packet) => match packet { - // GamePacket::ClientboundKeepAlivePacket(p) => { - // println!("Got keep alive packet {:?}", p.keep_alive_id); - // } - // GamePacket::ClientboundChatMessagePacket(p) => { - // println!("Got chat message packet {:?}", p.message); - // } - // _ => panic!("unhandled packet"), - // }, - // Err(e) => { - // println!("Error: {:?}", e); - // } - // } - // } + loop { + let packet_result = conn.read().await; + match packet_result { + Ok(packet) => match packet { + GamePacket::ClientboundLoginPacket(p) => { + println!("Got login packet {:?}", p); + } + // GamePacket::ClientboundKeepAlivePacket(p) => { + // println!("Got keep alive packet {:?}", p.keep_alive_id); + // } + // GamePacket::ClientboundChatMessagePacket(p) => { + // println!("Got chat message packet {:?}", p.message); + // } + _ => panic!("unhandled packet"), + }, + Err(e) => { + println!("Error: {:?}", e); + } + } + } Ok(()) } diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs index 61abf898..b6ff479d 100644 --- a/azalea-core/src/game_type.rs +++ b/azalea-core/src/game_type.rs @@ -26,14 +26,21 @@ impl GameType { } } - pub fn from_id(id: u8) -> GameType { - match id { + pub fn from_id(id: u8) -> Result { + Ok(match id { 0 => GameType::SURVIVAL, 1 => GameType::CREATIVE, 2 => GameType::ADVENTURE, 3 => GameType::SPECTATOR, - _ => panic!("Unknown game type id: {}", id), - } + _ => return Err(format!("Unknown game type id: {}", id)), + }) + } + + pub fn from_optional_id(id: i8) -> Result, String> { + Ok(match id { + -1 => None, + id => Some(GameType::from_id(id as u8)?), + }) } pub fn short_name(&self) -> &'static str { diff --git a/azalea-core/src/registries.rs b/azalea-core/src/registries.rs deleted file mode 100644 index 605d7770..00000000 --- a/azalea-core/src/registries.rs +++ /dev/null @@ -1,8 +0,0 @@ -struct RegistryHolder {} - -fn make_network_codec() -> NetworkCodec { - // Codec codec = ResourceLocation.CODEC.xmap(ResourceKey::createRegistryKey, ResourceKey::location); - // Codec codec2 = codec.partialDispatch("type", mappedRegistry -> DataResult.success(mappedRegistry.key()), resourceKey -> RegistryHolder.getNetworkCodec(resourceKey).map(codec -> MappedRegistry.networkCodec(resourceKey, Lifecycle.experimental(), codec))); - // UnboundedMapCodec unboundedMapCodec = Codec.unboundedMap((Codec)codec, (Codec)codec2); - // return RegistryHolder.captureMap(unboundedMapCodec); -} diff --git a/azalea-core/src/resource_location.rs b/azalea-core/src/resource_location.rs index c1fed451..7e28a2a2 100644 --- a/azalea-core/src/resource_location.rs +++ b/azalea-core/src/resource_location.rs @@ -1,6 +1,6 @@ //! A resource, like minecraft:stone -#[derive(Hash, Clone, Debug)] +#[derive(Hash, Clone, Debug, PartialEq, Eq)] pub struct ResourceLocation { pub namespace: String, pub path: String, diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs index b21bd9a5..41689a46 100644 --- a/azalea-nbt/src/decode.rs +++ b/azalea-nbt/src/decode.rs @@ -121,7 +121,18 @@ impl Tag { R: AsyncRead + std::marker::Unpin + std::marker::Send, { // default to compound tag - Tag::read_known(stream, 10).await + + // the parent compound only ever has one item + let tag_id = stream.read_u8().await.unwrap_or(0); + if tag_id == 0 { + return Ok(Tag::End); + } + let name = read_string(stream).await?; + let tag = Tag::read_known(stream, tag_id).await?; + let mut map = HashMap::with_capacity(1); + map.insert(name, tag); + + Ok(Tag::Compound(map)) } pub async fn read_zlib(stream: &mut R) -> Result diff --git a/azalea-protocol/src/mc_buf.rs b/azalea-protocol/src/mc_buf.rs index cf98aca3..04857235 100644 --- a/azalea-protocol/src/mc_buf.rs +++ b/azalea-protocol/src/mc_buf.rs @@ -3,6 +3,7 @@ use std::io::Write; use async_trait::async_trait; +use azalea_core::resource_location::ResourceLocation; use byteorder::{BigEndian, WriteBytesExt}; use tokio::io::{AsyncRead, AsyncReadExt}; @@ -39,6 +40,11 @@ pub trait Writable { 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] @@ -140,6 +146,17 @@ impl Writable for Vec { 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::(self, n) + } + + fn write_resource_location( + &mut self, + location: &ResourceLocation, + ) -> Result<(), std::io::Error> { + self.write_utf(&location.to_string()) + } } #[async_trait] @@ -156,6 +173,8 @@ pub trait Readable { async fn read_int(&mut self) -> Result; async fn read_boolean(&mut self) -> Result; async fn read_nbt(&mut self) -> Result; + async fn read_long(&mut self) -> Result; + async fn read_resource_location(&mut self) -> Result; } #[async_trait] @@ -285,14 +304,32 @@ where } async fn read_nbt(&mut self) -> Result { - Ok(azalea_nbt::Tag::read(self).await.unwrap()) + 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 { + 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 { + // 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::io::Cursor; + use std::{collections::HashMap, io::Cursor}; use tokio::io::BufReader; #[test] @@ -387,4 +424,55 @@ mod tests { ] ); } + + #[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/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs index 54205c48..1b90ce14 100644 --- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs @@ -1,6 +1,7 @@ use super::GamePacket; use crate::mc_buf::{Readable, Writable}; use azalea_core::{game_type::GameType, resource_location::ResourceLocation}; +use tokio::io::AsyncReadExt; #[derive(Clone, Debug)] pub struct ClientboundLoginPacket { @@ -50,27 +51,73 @@ impl ClientboundLoginPacket { 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_utf(&resource_location.to_string()) + buf.write_resource_location(resource_location) })?; - self.registry_holder - .write(buf) - .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "write registry holder"))?; - + 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( buf: &mut T, ) -> Result { - let transaction_id = buf.read_varint().await? as u32; - let identifier = ResourceLocation::new(&buf.read_utf().await?)?; - let data = buf.read_bytes(1048576).await?; - panic!("not implemented"); - // Ok(ClientboundLoginPacket { - // transaction_id, - // identifier, - // data, - // } - // .get()) + 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/mod.rs b/azalea-protocol/src/packets/game/mod.rs index 932435d9..5697a0ad 100644 --- a/azalea-protocol/src/packets/game/mod.rs +++ b/azalea-protocol/src/packets/game/mod.rs @@ -25,16 +25,23 @@ impl ProtocolPacket for GamePacket { /// Read a packet by its id, ConnectionProtocol, and flow async fn read( - _id: u32, + id: u32, flow: &PacketFlow, - _buf: &mut T, + buf: &mut T, ) -> Result where Self: Sized, { - match flow { - PacketFlow::ServerToClient => Err("HandshakePacket::read not implemented".to_string()), - PacketFlow::ClientToServer => Err("HandshakePacket::read not implemented".to_string()), - } + 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)), + }, + }) } } diff --git a/azalea-protocol/src/packets/login/mod.rs b/azalea-protocol/src/packets/login/mod.rs index 65d94bed..4d490d08 100644 --- a/azalea-protocol/src/packets/login/mod.rs +++ b/azalea-protocol/src/packets/login/mod.rs @@ -68,11 +68,11 @@ impl ProtocolPacket for LoginPacket { ) .await? } - _ => return Err(format!("Unknown ServerToClient status packet id: {}", id)), + _ => 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 status packet id: {}", id)), + _ => return Err(format!("Unknown ClientToServer login packet id: {}", id)), }, }) }