From 6ae94b96e6d51e3bf251d4a01f17fa7d41c9500f Mon Sep 17 00:00:00 2001 From: mat Date: Mon, 20 Dec 2021 15:22:02 -0600 Subject: [PATCH] start adding nbt to the protocol --- Cargo.lock | 22 +++++ README.md | 6 +- azalea-core/src/game_type.rs | 18 ++-- azalea-nbt/Cargo.toml | 6 +- azalea-nbt/benches/my_benchmark.rs | 14 ++-- azalea-nbt/src/decode.rs | 84 +++++++++++-------- azalea-nbt/src/error.rs | 10 +++ azalea-nbt/tests/tests.rs | 61 +++++++------- azalea-protocol/Cargo.toml | 1 + azalea-protocol/src/mc_buf.rs | 42 ++++++++-- .../packets/game/clientbound_login_packet.rs | 32 +++++-- 11 files changed, 206 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10c936ff..c037630d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,11 +95,14 @@ dependencies = [ name = "azalea-nbt" version = "0.1.0" dependencies = [ + "async-compression", + "async-recursion", "byteorder", "criterion", "flate2", "num-derive", "num-traits", + "tokio", ] [[package]] @@ -112,6 +115,7 @@ dependencies = [ "azalea-auth", "azalea-chat", "azalea-core", + "azalea-nbt", "byteorder", "bytes", "serde", @@ -214,6 +218,7 @@ dependencies = [ "clap", "criterion-plot", "csv", + "futures", "itertools", "lazy_static", "num-traits", @@ -226,6 +231,7 @@ dependencies = [ "serde_derive", "serde_json", "tinytemplate", + "tokio", "walkdir", ] @@ -351,6 +357,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.18" @@ -358,6 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -391,6 +412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" dependencies = [ "futures-core", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", diff --git a/README.md b/README.md index 5e1e4ecc..3ae4b307 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # Azalea -A Minecraft botting library in Rust. I named this Azalea because it sounds like a cool word and this is a cool library. This project was heavily inspired by PrismarineJS. +A Rust library for creating Minecraft bots. + +I named this Azalea because it sounds like a cool word and this is a cool library. This project was heavily inspired by PrismarineJS. ## Goals -- Bypass all anticheats +- Bypass most anticheats - Only support the latest Minecraft version - Do everything a vanilla client can do - Be fast diff --git a/azalea-core/src/game_type.rs b/azalea-core/src/game_type.rs index b0ca6a2d..61abf898 100644 --- a/azalea-core/src/game_type.rs +++ b/azalea-core/src/game_type.rs @@ -9,7 +9,7 @@ pub enum GameType { } impl GameType { - fn to_id(&self) -> u8 { + pub fn to_id(&self) -> u8 { match self { GameType::SURVIVAL => 0, GameType::CREATIVE => 1, @@ -18,7 +18,15 @@ impl GameType { } } - fn from_id(id: u8) -> GameType { + /// Get the id of the game type, but return -1 if the game type is invalid. + pub fn to_optional_id(game_type: &Option) -> i8 { + match game_type { + Some(game_type) => game_type.to_id() as i8, + None => -1, + } + } + + pub fn from_id(id: u8) -> GameType { match id { 0 => GameType::SURVIVAL, 1 => GameType::CREATIVE, @@ -28,7 +36,7 @@ impl GameType { } } - fn short_name(&self) -> &'static str { + pub fn short_name(&self) -> &'static str { // TODO: these should be translated TranslatableComponent("selectWorld.gameMode." + string2) match self { GameType::SURVIVAL => "Survival", @@ -38,7 +46,7 @@ impl GameType { } } - fn long_name(&self) -> &'static str { + pub fn long_name(&self) -> &'static str { // TODO: These should be translated TranslatableComponent("gameMode." + string2); match self { GameType::SURVIVAL => "Survival Mode", @@ -48,7 +56,7 @@ impl GameType { } } - fn from_name(name: &str) -> GameType { + pub fn from_name(name: &str) -> GameType { match name { "survival" => GameType::SURVIVAL, "creative" => GameType::CREATIVE, diff --git a/azalea-nbt/Cargo.toml b/azalea-nbt/Cargo.toml index bdc4208c..5d28d136 100644 --- a/azalea-nbt/Cargo.toml +++ b/azalea-nbt/Cargo.toml @@ -6,13 +6,17 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-compression = {version = "^0.3.8", features = ["tokio", "zlib", "gzip"]} +async-recursion = "^0.3.2" byteorder = "^1.4.3" flate2 = "^1.0.22" num-derive = "^0.3.3" num-traits = "^0.2.14" +tokio = "^1.15.0" [dev-dependencies] -criterion = {version = "^0.3.5", features = ["html_reports"]} +criterion = {version = "^0.3.5", features = ["html_reports", "async_tokio"]} +tokio = {version = "^1.15.0", features = ["fs"]} [profile.release] lto = true diff --git a/azalea-nbt/benches/my_benchmark.rs b/azalea-nbt/benches/my_benchmark.rs index 46a2f851..1e642716 100644 --- a/azalea-nbt/benches/my_benchmark.rs +++ b/azalea-nbt/benches/my_benchmark.rs @@ -19,15 +19,19 @@ fn bench_serialize(filename: &str, c: &mut Criterion) { let mut decoded_src_stream = std::io::Cursor::new(decoded_src.clone()); file.seek(SeekFrom::Start(0)).unwrap(); - let nbt = Tag::read_gzip(&mut file).unwrap(); + // run Tag::read(&mut decoded_src_stream) asynchronously + let nbt = tokio::runtime::Runtime::new() + .unwrap() + .block_on(async { Tag::read(&mut decoded_src_stream).await.unwrap() }); let mut group = c.benchmark_group(filename); group.throughput(Throughput::Bytes(decoded_src.len() as u64)); group.bench_function("Decode", |b| { - b.iter(|| { - decoded_src_stream.seek(SeekFrom::Start(0)).unwrap(); - Tag::read(&mut decoded_src_stream).unwrap(); - }) + b.to_async(tokio::runtime::Runtime::new().unwrap()) + .iter(|| { + decoded_src_stream.seek(SeekFrom::Start(0)).unwrap(); + Tag::read(&mut decoded_src_stream) + }) }); group.bench_function("Encode", |b| { b.iter(|| { diff --git a/azalea-nbt/src/decode.rs b/azalea-nbt/src/decode.rs index fb1b78b6..b21bd9a5 100644 --- a/azalea-nbt/src/decode.rs +++ b/azalea-nbt/src/decode.rs @@ -1,54 +1,63 @@ use crate::Error; use crate::Tag; -use byteorder::{ReadBytesExt, BE}; -use flate2::read::{GzDecoder, ZlibDecoder}; -use std::{collections::HashMap, io::Read}; +use async_compression::tokio::bufread::{GzipDecoder, ZlibDecoder}; +use async_recursion::async_recursion; +use std::collections::HashMap; +use tokio::io::AsyncBufRead; +use tokio::io::{AsyncRead, AsyncReadExt}; #[inline] -fn read_string(stream: &mut impl Read) -> Result { - let length = stream.read_u16::().map_err(|_| Error::InvalidTag)?; +async fn read_string(stream: &mut R) -> Result +where + R: AsyncRead + std::marker::Unpin, +{ + let length = stream.read_u16().await.map_err(|_| Error::InvalidTag)?; let mut buf = Vec::with_capacity(length as usize); for _ in 0..length { - buf.push(stream.read_u8().map_err(|_| Error::InvalidTag)?); + buf.push(stream.read_u8().await.map_err(|_| Error::InvalidTag)?); } String::from_utf8(buf).map_err(|_| Error::InvalidTag) } impl Tag { - fn read_known(stream: &mut impl Read, id: u8) -> Result { + #[async_recursion] + async fn read_known(stream: &mut R, id: u8) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { let tag = match id { // Signifies the end of a TAG_Compound. It is only ever used inside // a TAG_Compound, and is not named despite being in a TAG_Compound 0 => Tag::End, // A single signed byte - 1 => Tag::Byte(stream.read_i8().map_err(|_| Error::InvalidTag)?), + 1 => Tag::Byte(stream.read_i8().await.map_err(|_| Error::InvalidTag)?), // A single signed, big endian 16 bit integer - 2 => Tag::Short(stream.read_i16::().map_err(|_| Error::InvalidTag)?), + 2 => Tag::Short(stream.read_i16().await.map_err(|_| Error::InvalidTag)?), // A single signed, big endian 32 bit integer - 3 => Tag::Int(stream.read_i32::().map_err(|_| Error::InvalidTag)?), + 3 => Tag::Int(stream.read_i32().await.map_err(|_| Error::InvalidTag)?), // A single signed, big endian 64 bit integer - 4 => Tag::Long(stream.read_i64::().map_err(|_| Error::InvalidTag)?), + 4 => Tag::Long(stream.read_i64().await.map_err(|_| Error::InvalidTag)?), // A single, big endian IEEE-754 single-precision floating point // number (NaN possible) - 5 => Tag::Float(stream.read_f32::().map_err(|_| Error::InvalidTag)?), + 5 => Tag::Float(stream.read_f32().await.map_err(|_| Error::InvalidTag)?), // A single, big endian IEEE-754 double-precision floating point // number (NaN possible) - 6 => Tag::Double(stream.read_f64::().map_err(|_| Error::InvalidTag)?), + 6 => Tag::Double(stream.read_f64().await.map_err(|_| Error::InvalidTag)?), // A length-prefixed array of signed bytes. The prefix is a signed // integer (thus 4 bytes) 7 => { - let length = stream.read_i32::().map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut bytes = Vec::with_capacity(length as usize); for _ in 0..length { - bytes.push(stream.read_i8().map_err(|_| Error::InvalidTag)?); + bytes.push(stream.read_i8().await.map_err(|_| Error::InvalidTag)?); } Tag::ByteArray(bytes) } // A length-prefixed modified UTF-8 string. The prefix is an // unsigned short (thus 2 bytes) signifying the length of the // string in bytes - 8 => Tag::String(read_string(stream)?), + 8 => Tag::String(read_string(stream).await?), // A list of nameless tags, all of the same type. The list is // prefixed with the Type ID of the items it contains (thus 1 // byte), and the length of the list as a signed integer (a further @@ -58,11 +67,11 @@ impl Tag { // another reference implementation by Mojang uses 1 instead; // parsers should accept any type if the length is <= 0). 9 => { - let type_id = stream.read_u8().map_err(|_| Error::InvalidTag)?; - let length = stream.read_i32::().map_err(|_| Error::InvalidTag)?; + let type_id = stream.read_u8().await.map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut list = Vec::with_capacity(length as usize); for _ in 0..length { - list.push(Tag::read_known(stream, type_id)?); + list.push(Tag::read_known(stream, type_id).await?); } Tag::List(list) } @@ -71,12 +80,12 @@ impl Tag { // we default to capacity 4 because it'll probably not be empty let mut map = HashMap::with_capacity(4); loop { - let tag_id = stream.read_u8().unwrap_or(0); + let tag_id = stream.read_u8().await.unwrap_or(0); if tag_id == 0 { break; } - let name = read_string(stream)?; - let tag = Tag::read_known(stream, tag_id)?; + let name = read_string(stream).await?; + let tag = Tag::read_known(stream, tag_id).await?; map.insert(name, tag); } Tag::Compound(map) @@ -85,20 +94,20 @@ impl Tag { // signed integer (thus 4 bytes) and indicates the number of 4 byte // integers. 11 => { - let length = stream.read_i32::().map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut ints = Vec::with_capacity(length as usize); for _ in 0..length { - ints.push(stream.read_i32::().map_err(|_| Error::InvalidTag)?); + ints.push(stream.read_i32().await.map_err(|_| Error::InvalidTag)?); } Tag::IntArray(ints) } // A length-prefixed array of signed longs. The prefix is a signed // integer (thus 4 bytes) and indicates the number of 8 byte longs. 12 => { - let length = stream.read_i32::().map_err(|_| Error::InvalidTag)?; + let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?; let mut longs = Vec::with_capacity(length as usize); for _ in 0..length { - longs.push(stream.read_i64::().map_err(|_| Error::InvalidTag)?); + longs.push(stream.read_i64().await.map_err(|_| Error::InvalidTag)?); } Tag::LongArray(longs) } @@ -107,18 +116,27 @@ impl Tag { Ok(tag) } - pub fn read(stream: &mut impl Read) -> Result { + pub async fn read(stream: &mut R) -> Result + where + R: AsyncRead + std::marker::Unpin + std::marker::Send, + { // default to compound tag - Tag::read_known(stream, 10) + Tag::read_known(stream, 10).await } - pub fn read_zlib(stream: &mut impl Read) -> Result { + pub async fn read_zlib(stream: &mut R) -> Result + where + R: AsyncBufRead + std::marker::Unpin + std::marker::Send, + { let mut gz = ZlibDecoder::new(stream); - Tag::read(&mut gz) + Tag::read(&mut gz).await } - pub fn read_gzip(stream: &mut impl Read) -> Result { - let mut gz = GzDecoder::new(stream); - Tag::read(&mut gz) + pub async fn read_gzip(stream: &mut R) -> Result + where + R: AsyncBufRead + std::marker::Unpin + std::marker::Send, + { + let mut gz = GzipDecoder::new(stream); + Tag::read(&mut gz).await } } diff --git a/azalea-nbt/src/error.rs b/azalea-nbt/src/error.rs index 3ada7cf7..05ff15e0 100644 --- a/azalea-nbt/src/error.rs +++ b/azalea-nbt/src/error.rs @@ -4,3 +4,13 @@ pub enum Error { InvalidTag, WriteError, } + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::InvalidTagType(id) => write!(f, "Invalid tag type: {}", id), + Error::InvalidTag => write!(f, "Invalid tag"), + Error::WriteError => write!(f, "Write error"), + } + } +} diff --git a/azalea-nbt/tests/tests.rs b/azalea-nbt/tests/tests.rs index dd2bb6dd..75b3a646 100644 --- a/azalea-nbt/tests/tests.rs +++ b/azalea-nbt/tests/tests.rs @@ -3,12 +3,13 @@ use std::{ collections::HashMap, io::{Cursor, Read}, }; +use tokio::{fs::File, io::AsyncReadExt}; -#[test] -fn test_decode_hello_world() { +#[tokio::test] +async fn test_decode_hello_world() { // read hello_world.nbt - let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap(); - let tag = Tag::read(&mut file).unwrap(); + let mut file = File::open("tests/hello_world.nbt").await.unwrap(); + let tag = Tag::read(&mut file).await.unwrap(); assert_eq!( tag, Tag::Compound(HashMap::from_iter(vec![( @@ -21,14 +22,14 @@ fn test_decode_hello_world() { ); } -#[test] -fn test_roundtrip_hello_world() { - let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap(); +#[tokio::test] +async fn test_roundtrip_hello_world() { + let mut file = File::open("tests/hello_world.nbt").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let tag = Tag::read(&mut original_stream).unwrap(); + let tag = Tag::read(&mut original_stream).await.unwrap(); // write hello_world.nbt let mut result = Cursor::new(Vec::new()); @@ -37,26 +38,26 @@ fn test_roundtrip_hello_world() { assert_eq!(result.into_inner(), original); } -#[test] -fn test_bigtest() { +#[tokio::test] +async fn test_bigtest() { // read bigtest.nbt - let mut file = std::fs::File::open("tests/bigtest.nbt").unwrap(); + let mut file = File::open("tests/bigtest.nbt").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); let mut result = Vec::new(); original_tag.write(&mut result).unwrap(); - let decoded_tag = Tag::read(&mut Cursor::new(result)).unwrap(); + let decoded_tag = Tag::read(&mut Cursor::new(result)).await.unwrap(); assert_eq!(decoded_tag, original_tag); } -#[test] -fn test_stringtest() { +#[tokio::test] +async fn test_stringtest() { let correct_tag = Tag::Compound(HashMap::from_iter(vec![( "😃".to_string(), Tag::List(vec![ @@ -83,41 +84,41 @@ fn test_stringtest() { file.read_to_end(&mut original).unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); assert_eq!(original_tag, correct_tag); } -#[test] -fn test_complex_player() { - let mut file = std::fs::File::open("tests/complex_player.dat").unwrap(); +#[tokio::test] +async fn test_complex_player() { + let mut file = File::open("tests/complex_player.dat").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); let mut result = Vec::new(); original_tag.write(&mut result).unwrap(); - let decoded_tag = Tag::read(&mut Cursor::new(result)).unwrap(); + let decoded_tag = Tag::read(&mut Cursor::new(result)).await.unwrap(); assert_eq!(decoded_tag, original_tag); } -#[test] -fn test_simple_player() { - let mut file = std::fs::File::open("tests/simple_player.dat").unwrap(); +#[tokio::test] +async fn test_simple_player() { + let mut file = File::open("tests/simple_player.dat").await.unwrap(); let mut original = Vec::new(); - file.read_to_end(&mut original).unwrap(); + file.read_to_end(&mut original).await.unwrap(); let mut original_stream = Cursor::new(original.clone()); - let original_tag = Tag::read_gzip(&mut original_stream).unwrap(); + let original_tag = Tag::read_gzip(&mut original_stream).await.unwrap(); let mut result = Vec::new(); original_tag.write(&mut result).unwrap(); - let decoded_tag = Tag::read(&mut Cursor::new(result)).unwrap(); + let decoded_tag = Tag::read(&mut Cursor::new(result)).await.unwrap(); assert_eq!(decoded_tag, original_tag); } diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index 272816e2..c9883195 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -12,6 +12,7 @@ async-trait = "0.1.51" azalea-auth = {path = "../azalea-auth"} azalea-chat = {path = "../azalea-chat"} azalea-core = {path = "../azalea-core"} +azalea-nbt = {path = "../azalea-nbt"} byteorder = "^1.4.3" bytes = "^1.1.0" serde = {version = "1.0.130", features = ["serde_derive"]} diff --git a/azalea-protocol/src/mc_buf.rs b/azalea-protocol/src/mc_buf.rs index 84c602ec..3959560d 100644 --- a/azalea-protocol/src/mc_buf.rs +++ b/azalea-protocol/src/mc_buf.rs @@ -12,9 +12,10 @@ const MAX_STRING_LENGTH: u16 = 32767; #[async_trait] pub trait Writable { - fn write_list(&mut self, list: Vec, writer: F) -> Result<(), std::io::Error> + fn write_list(&mut self, list: &Vec, writer: F) -> Result<(), std::io::Error> where - F: FnOnce(&mut Self, T) -> Result<(), std::io::Error> + Copy, + F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, + T: Sized, Self: Sized; fn write_int_id_list(&mut self, list: Vec) -> Result<(), std::io::Error>; fn write_map( @@ -36,13 +37,15 @@ pub trait Writable { 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>; } #[async_trait] impl Writable for Vec { - fn write_list(&mut self, list: Vec, writer: F) -> Result<(), std::io::Error> + fn write_list(&mut self, list: &Vec, writer: F) -> Result<(), std::io::Error> where - F: FnOnce(&mut Self, T) -> Result<(), std::io::Error> + Copy, + F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy, Self: Sized, { self.write_varint(list.len() as i32)?; @@ -53,7 +56,7 @@ impl Writable for Vec { } fn write_int_id_list(&mut self, list: Vec) -> Result<(), std::io::Error> { - self.write_list(list, Self::write_varint) + self.write_list(&list, |buf, n| buf.write_varint(*n)) } fn write_map( @@ -128,6 +131,15 @@ impl Writable for Vec { fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> { WriteBytesExt::write_i32::(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())) + } } #[async_trait] @@ -142,6 +154,8 @@ pub trait Readable { async fn read_utf_with_len(&mut self, max_length: u32) -> Result; async fn read_byte(&mut self) -> Result; async fn read_int(&mut self) -> Result; + async fn read_boolean(&mut self) -> Result; + async fn read_nbt(&mut self) -> Result; } #[async_trait] @@ -261,6 +275,19 @@ where Err(_) => Err("Error reading int".to_string()), } } + + async fn read_boolean(&mut self) -> Result { + 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 { + self.peek(); + Ok(azalea_nbt::Tag::read(self).unwrap()) + } } #[cfg(test)] @@ -301,7 +328,7 @@ mod tests { #[tokio::test] async fn test_list() { let mut buf = Vec::new(); - buf.write_list(vec!["a", "bc", "def"], Vec::write_utf) + 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 @@ -319,7 +346,8 @@ mod tests { #[tokio::test] async fn test_int_id_list() { let mut buf = Vec::new(); - buf.write_list(vec![1, 2, 3], Vec::write_varint).unwrap(); + buf.write_list(&vec![1, 2, 3], |buf, i| buf.write_varint(*i)) + .unwrap(); let mut buf = BufReader::new(Cursor::new(buf)); diff --git a/azalea-protocol/src/packets/game/clientbound_login_packet.rs b/azalea-protocol/src/packets/game/clientbound_login_packet.rs index 0e23460e..54205c48 100644 --- a/azalea-protocol/src/packets/game/clientbound_login_packet.rs +++ b/azalea-protocol/src/packets/game/clientbound_login_packet.rs @@ -1,9 +1,8 @@ use super::GamePacket; use crate::mc_buf::{Readable, Writable}; use azalea_core::{game_type::GameType, resource_location::ResourceLocation}; -use std::hash::Hash; -#[derive(Hash, Clone, Debug)] +#[derive(Clone, Debug)] pub struct ClientboundLoginPacket { // private final int playerId; // private final boolean hardcore; @@ -27,7 +26,17 @@ pub struct ClientboundLoginPacket { pub game_type: GameType, pub previous_game_type: Option, pub levels: Vec, - // pub registry_holder: azalea_core::registry::RegistryAccess, + pub registry_holder: azalea_nbt::Tag, + pub dimension_type: azalea_nbt::Tag, + pub dimension: ResourceLocation, + pub seed: i64, + pub max_players: i32, + pub chunk_radius: i32, + pub simulation_distance: i32, + pub reduced_debug_info: bool, + pub show_death_screen: bool, + pub is_debug: bool, + pub is_flat: bool, } impl ClientboundLoginPacket { @@ -35,10 +44,19 @@ impl ClientboundLoginPacket { GamePacket::ClientboundLoginPacket(self) } - pub fn write(&self, buf: &mut Vec) { - buf.write_int(self.player_id); - // buf.write_bool(self.hardcore); - // buf.write_byte(self.game_type. + pub fn write(&self, buf: &mut Vec) -> 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_utf(&resource_location.to_string()) + })?; + self.registry_holder + .write(buf) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "write registry holder"))?; + + Ok(()) } pub async fn read(