mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
start adding nbt to the protocol
This commit is contained in:
parent
cf88c7b795
commit
6ae94b96e6
11 changed files with 206 additions and 90 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -95,11 +95,14 @@ dependencies = [
|
||||||
name = "azalea-nbt"
|
name = "azalea-nbt"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
|
"async-recursion",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"criterion",
|
"criterion",
|
||||||
"flate2",
|
"flate2",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -112,6 +115,7 @@ dependencies = [
|
||||||
"azalea-auth",
|
"azalea-auth",
|
||||||
"azalea-chat",
|
"azalea-chat",
|
||||||
"azalea-core",
|
"azalea-core",
|
||||||
|
"azalea-nbt",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -214,6 +218,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"criterion-plot",
|
"criterion-plot",
|
||||||
"csv",
|
"csv",
|
||||||
|
"futures",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
@ -226,6 +231,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tinytemplate",
|
"tinytemplate",
|
||||||
|
"tokio",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -351,6 +357,20 @@ dependencies = [
|
||||||
"percent-encoding",
|
"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]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
|
@ -358,6 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -391,6 +412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# Azalea
|
# 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
|
## Goals
|
||||||
|
|
||||||
- Bypass all anticheats
|
- Bypass most anticheats
|
||||||
- Only support the latest Minecraft version
|
- Only support the latest Minecraft version
|
||||||
- Do everything a vanilla client can do
|
- Do everything a vanilla client can do
|
||||||
- Be fast
|
- Be fast
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub enum GameType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameType {
|
impl GameType {
|
||||||
fn to_id(&self) -> u8 {
|
pub fn to_id(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
GameType::SURVIVAL => 0,
|
GameType::SURVIVAL => 0,
|
||||||
GameType::CREATIVE => 1,
|
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<GameType>) -> i8 {
|
||||||
|
match game_type {
|
||||||
|
Some(game_type) => game_type.to_id() as i8,
|
||||||
|
None => -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_id(id: u8) -> GameType {
|
||||||
match id {
|
match id {
|
||||||
0 => GameType::SURVIVAL,
|
0 => GameType::SURVIVAL,
|
||||||
1 => GameType::CREATIVE,
|
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)
|
// TODO: these should be translated TranslatableComponent("selectWorld.gameMode." + string2)
|
||||||
match self {
|
match self {
|
||||||
GameType::SURVIVAL => "Survival",
|
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);
|
// TODO: These should be translated TranslatableComponent("gameMode." + string2);
|
||||||
match self {
|
match self {
|
||||||
GameType::SURVIVAL => "Survival Mode",
|
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 {
|
match name {
|
||||||
"survival" => GameType::SURVIVAL,
|
"survival" => GameType::SURVIVAL,
|
||||||
"creative" => GameType::CREATIVE,
|
"creative" => GameType::CREATIVE,
|
||||||
|
|
|
@ -6,13 +6,17 @@ version = "0.1.0"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-compression = {version = "^0.3.8", features = ["tokio", "zlib", "gzip"]}
|
||||||
|
async-recursion = "^0.3.2"
|
||||||
byteorder = "^1.4.3"
|
byteorder = "^1.4.3"
|
||||||
flate2 = "^1.0.22"
|
flate2 = "^1.0.22"
|
||||||
num-derive = "^0.3.3"
|
num-derive = "^0.3.3"
|
||||||
num-traits = "^0.2.14"
|
num-traits = "^0.2.14"
|
||||||
|
tokio = "^1.15.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
@ -19,15 +19,19 @@ fn bench_serialize(filename: &str, c: &mut Criterion) {
|
||||||
let mut decoded_src_stream = std::io::Cursor::new(decoded_src.clone());
|
let mut decoded_src_stream = std::io::Cursor::new(decoded_src.clone());
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
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);
|
let mut group = c.benchmark_group(filename);
|
||||||
group.throughput(Throughput::Bytes(decoded_src.len() as u64));
|
group.throughput(Throughput::Bytes(decoded_src.len() as u64));
|
||||||
group.bench_function("Decode", |b| {
|
group.bench_function("Decode", |b| {
|
||||||
b.iter(|| {
|
b.to_async(tokio::runtime::Runtime::new().unwrap())
|
||||||
decoded_src_stream.seek(SeekFrom::Start(0)).unwrap();
|
.iter(|| {
|
||||||
Tag::read(&mut decoded_src_stream).unwrap();
|
decoded_src_stream.seek(SeekFrom::Start(0)).unwrap();
|
||||||
})
|
Tag::read(&mut decoded_src_stream)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
group.bench_function("Encode", |b| {
|
group.bench_function("Encode", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
|
|
|
@ -1,54 +1,63 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use crate::Tag;
|
use crate::Tag;
|
||||||
use byteorder::{ReadBytesExt, BE};
|
use async_compression::tokio::bufread::{GzipDecoder, ZlibDecoder};
|
||||||
use flate2::read::{GzDecoder, ZlibDecoder};
|
use async_recursion::async_recursion;
|
||||||
use std::{collections::HashMap, io::Read};
|
use std::collections::HashMap;
|
||||||
|
use tokio::io::AsyncBufRead;
|
||||||
|
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read_string(stream: &mut impl Read) -> Result<String, Error> {
|
async fn read_string<R>(stream: &mut R) -> Result<String, Error>
|
||||||
let length = stream.read_u16::<BE>().map_err(|_| Error::InvalidTag)?;
|
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);
|
let mut buf = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
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)
|
String::from_utf8(buf).map_err(|_| Error::InvalidTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
impl Tag {
|
||||||
fn read_known(stream: &mut impl Read, id: u8) -> Result<Tag, Error> {
|
#[async_recursion]
|
||||||
|
async fn read_known<R>(stream: &mut R, id: u8) -> Result<Tag, Error>
|
||||||
|
where
|
||||||
|
R: AsyncRead + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
let tag = match id {
|
let tag = match id {
|
||||||
// Signifies the end of a TAG_Compound. It is only ever used inside
|
// 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
|
// a TAG_Compound, and is not named despite being in a TAG_Compound
|
||||||
0 => Tag::End,
|
0 => Tag::End,
|
||||||
// A single signed byte
|
// 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
|
// A single signed, big endian 16 bit integer
|
||||||
2 => Tag::Short(stream.read_i16::<BE>().map_err(|_| Error::InvalidTag)?),
|
2 => Tag::Short(stream.read_i16().await.map_err(|_| Error::InvalidTag)?),
|
||||||
// A single signed, big endian 32 bit integer
|
// A single signed, big endian 32 bit integer
|
||||||
3 => Tag::Int(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?),
|
3 => Tag::Int(stream.read_i32().await.map_err(|_| Error::InvalidTag)?),
|
||||||
// A single signed, big endian 64 bit integer
|
// A single signed, big endian 64 bit integer
|
||||||
4 => Tag::Long(stream.read_i64::<BE>().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
|
// A single, big endian IEEE-754 single-precision floating point
|
||||||
// number (NaN possible)
|
// number (NaN possible)
|
||||||
5 => Tag::Float(stream.read_f32::<BE>().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
|
// A single, big endian IEEE-754 double-precision floating point
|
||||||
// number (NaN possible)
|
// number (NaN possible)
|
||||||
6 => Tag::Double(stream.read_f64::<BE>().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
|
// A length-prefixed array of signed bytes. The prefix is a signed
|
||||||
// integer (thus 4 bytes)
|
// integer (thus 4 bytes)
|
||||||
7 => {
|
7 => {
|
||||||
let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
||||||
let mut bytes = Vec::with_capacity(length as usize);
|
let mut bytes = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
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)
|
Tag::ByteArray(bytes)
|
||||||
}
|
}
|
||||||
// A length-prefixed modified UTF-8 string. The prefix is an
|
// A length-prefixed modified UTF-8 string. The prefix is an
|
||||||
// unsigned short (thus 2 bytes) signifying the length of the
|
// unsigned short (thus 2 bytes) signifying the length of the
|
||||||
// string in bytes
|
// 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
|
// 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
|
// 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
|
// 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;
|
// another reference implementation by Mojang uses 1 instead;
|
||||||
// parsers should accept any type if the length is <= 0).
|
// parsers should accept any type if the length is <= 0).
|
||||||
9 => {
|
9 => {
|
||||||
let type_id = stream.read_u8().map_err(|_| Error::InvalidTag)?;
|
let type_id = stream.read_u8().await.map_err(|_| Error::InvalidTag)?;
|
||||||
let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
||||||
let mut list = Vec::with_capacity(length as usize);
|
let mut list = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
list.push(Tag::read_known(stream, type_id)?);
|
list.push(Tag::read_known(stream, type_id).await?);
|
||||||
}
|
}
|
||||||
Tag::List(list)
|
Tag::List(list)
|
||||||
}
|
}
|
||||||
|
@ -71,12 +80,12 @@ impl Tag {
|
||||||
// we default to capacity 4 because it'll probably not be empty
|
// we default to capacity 4 because it'll probably not be empty
|
||||||
let mut map = HashMap::with_capacity(4);
|
let mut map = HashMap::with_capacity(4);
|
||||||
loop {
|
loop {
|
||||||
let tag_id = stream.read_u8().unwrap_or(0);
|
let tag_id = stream.read_u8().await.unwrap_or(0);
|
||||||
if tag_id == 0 {
|
if tag_id == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let name = read_string(stream)?;
|
let name = read_string(stream).await?;
|
||||||
let tag = Tag::read_known(stream, tag_id)?;
|
let tag = Tag::read_known(stream, tag_id).await?;
|
||||||
map.insert(name, tag);
|
map.insert(name, tag);
|
||||||
}
|
}
|
||||||
Tag::Compound(map)
|
Tag::Compound(map)
|
||||||
|
@ -85,20 +94,20 @@ impl Tag {
|
||||||
// signed integer (thus 4 bytes) and indicates the number of 4 byte
|
// signed integer (thus 4 bytes) and indicates the number of 4 byte
|
||||||
// integers.
|
// integers.
|
||||||
11 => {
|
11 => {
|
||||||
let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
||||||
let mut ints = Vec::with_capacity(length as usize);
|
let mut ints = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
ints.push(stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?);
|
ints.push(stream.read_i32().await.map_err(|_| Error::InvalidTag)?);
|
||||||
}
|
}
|
||||||
Tag::IntArray(ints)
|
Tag::IntArray(ints)
|
||||||
}
|
}
|
||||||
// A length-prefixed array of signed longs. The prefix is a signed
|
// A length-prefixed array of signed longs. The prefix is a signed
|
||||||
// integer (thus 4 bytes) and indicates the number of 8 byte longs.
|
// integer (thus 4 bytes) and indicates the number of 8 byte longs.
|
||||||
12 => {
|
12 => {
|
||||||
let length = stream.read_i32::<BE>().map_err(|_| Error::InvalidTag)?;
|
let length = stream.read_i32().await.map_err(|_| Error::InvalidTag)?;
|
||||||
let mut longs = Vec::with_capacity(length as usize);
|
let mut longs = Vec::with_capacity(length as usize);
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
longs.push(stream.read_i64::<BE>().map_err(|_| Error::InvalidTag)?);
|
longs.push(stream.read_i64().await.map_err(|_| Error::InvalidTag)?);
|
||||||
}
|
}
|
||||||
Tag::LongArray(longs)
|
Tag::LongArray(longs)
|
||||||
}
|
}
|
||||||
|
@ -107,18 +116,27 @@ impl Tag {
|
||||||
Ok(tag)
|
Ok(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(stream: &mut impl Read) -> Result<Tag, Error> {
|
pub async fn read<R>(stream: &mut R) -> Result<Tag, Error>
|
||||||
|
where
|
||||||
|
R: AsyncRead + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
// default to compound tag
|
// default to compound tag
|
||||||
Tag::read_known(stream, 10)
|
Tag::read_known(stream, 10).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_zlib(stream: &mut impl Read) -> Result<Tag, Error> {
|
pub async fn read_zlib<R>(stream: &mut R) -> Result<Tag, Error>
|
||||||
|
where
|
||||||
|
R: AsyncBufRead + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
let mut gz = ZlibDecoder::new(stream);
|
let mut gz = ZlibDecoder::new(stream);
|
||||||
Tag::read(&mut gz)
|
Tag::read(&mut gz).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_gzip(stream: &mut impl Read) -> Result<Tag, Error> {
|
pub async fn read_gzip<R>(stream: &mut R) -> Result<Tag, Error>
|
||||||
let mut gz = GzDecoder::new(stream);
|
where
|
||||||
Tag::read(&mut gz)
|
R: AsyncBufRead + std::marker::Unpin + std::marker::Send,
|
||||||
|
{
|
||||||
|
let mut gz = GzipDecoder::new(stream);
|
||||||
|
Tag::read(&mut gz).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,13 @@ pub enum Error {
|
||||||
InvalidTag,
|
InvalidTag,
|
||||||
WriteError,
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{Cursor, Read},
|
io::{Cursor, Read},
|
||||||
};
|
};
|
||||||
|
use tokio::{fs::File, io::AsyncReadExt};
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_decode_hello_world() {
|
async fn test_decode_hello_world() {
|
||||||
// read hello_world.nbt
|
// read hello_world.nbt
|
||||||
let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap();
|
let mut file = File::open("tests/hello_world.nbt").await.unwrap();
|
||||||
let tag = Tag::read(&mut file).unwrap();
|
let tag = Tag::read(&mut file).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tag,
|
tag,
|
||||||
Tag::Compound(HashMap::from_iter(vec![(
|
Tag::Compound(HashMap::from_iter(vec![(
|
||||||
|
@ -21,14 +22,14 @@ fn test_decode_hello_world() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_roundtrip_hello_world() {
|
async fn test_roundtrip_hello_world() {
|
||||||
let mut file = std::fs::File::open("tests/hello_world.nbt").unwrap();
|
let mut file = File::open("tests/hello_world.nbt").await.unwrap();
|
||||||
let mut original = Vec::new();
|
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 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
|
// write hello_world.nbt
|
||||||
let mut result = Cursor::new(Vec::new());
|
let mut result = Cursor::new(Vec::new());
|
||||||
|
@ -37,26 +38,26 @@ fn test_roundtrip_hello_world() {
|
||||||
assert_eq!(result.into_inner(), original);
|
assert_eq!(result.into_inner(), original);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_bigtest() {
|
async fn test_bigtest() {
|
||||||
// read bigtest.nbt
|
// 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();
|
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 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();
|
let mut result = Vec::new();
|
||||||
original_tag.write(&mut result).unwrap();
|
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);
|
assert_eq!(decoded_tag, original_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_stringtest() {
|
async fn test_stringtest() {
|
||||||
let correct_tag = Tag::Compound(HashMap::from_iter(vec![(
|
let correct_tag = Tag::Compound(HashMap::from_iter(vec![(
|
||||||
"😃".to_string(),
|
"😃".to_string(),
|
||||||
Tag::List(vec![
|
Tag::List(vec![
|
||||||
|
@ -83,41 +84,41 @@ fn test_stringtest() {
|
||||||
file.read_to_end(&mut original).unwrap();
|
file.read_to_end(&mut original).unwrap();
|
||||||
|
|
||||||
let mut original_stream = Cursor::new(original.clone());
|
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);
|
assert_eq!(original_tag, correct_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_complex_player() {
|
async fn test_complex_player() {
|
||||||
let mut file = std::fs::File::open("tests/complex_player.dat").unwrap();
|
let mut file = File::open("tests/complex_player.dat").await.unwrap();
|
||||||
let mut original = Vec::new();
|
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 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();
|
let mut result = Vec::new();
|
||||||
original_tag.write(&mut result).unwrap();
|
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);
|
assert_eq!(decoded_tag, original_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_simple_player() {
|
async fn test_simple_player() {
|
||||||
let mut file = std::fs::File::open("tests/simple_player.dat").unwrap();
|
let mut file = File::open("tests/simple_player.dat").await.unwrap();
|
||||||
let mut original = Vec::new();
|
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 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();
|
let mut result = Vec::new();
|
||||||
original_tag.write(&mut result).unwrap();
|
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);
|
assert_eq!(decoded_tag, original_tag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ async-trait = "0.1.51"
|
||||||
azalea-auth = {path = "../azalea-auth"}
|
azalea-auth = {path = "../azalea-auth"}
|
||||||
azalea-chat = {path = "../azalea-chat"}
|
azalea-chat = {path = "../azalea-chat"}
|
||||||
azalea-core = {path = "../azalea-core"}
|
azalea-core = {path = "../azalea-core"}
|
||||||
|
azalea-nbt = {path = "../azalea-nbt"}
|
||||||
byteorder = "^1.4.3"
|
byteorder = "^1.4.3"
|
||||||
bytes = "^1.1.0"
|
bytes = "^1.1.0"
|
||||||
serde = {version = "1.0.130", features = ["serde_derive"]}
|
serde = {version = "1.0.130", features = ["serde_derive"]}
|
||||||
|
|
|
@ -12,9 +12,10 @@ const MAX_STRING_LENGTH: u16 = 32767;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Writable {
|
pub trait Writable {
|
||||||
fn write_list<F, T>(&mut self, list: Vec<T>, writer: F) -> Result<(), std::io::Error>
|
fn write_list<F, T>(&mut self, list: &Vec<T>, writer: F) -> Result<(), std::io::Error>
|
||||||
where
|
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;
|
Self: Sized;
|
||||||
fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error>;
|
fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error>;
|
||||||
fn write_map<KF, VF, KT, VT>(
|
fn write_map<KF, VF, KT, VT>(
|
||||||
|
@ -36,13 +37,15 @@ pub trait Writable {
|
||||||
fn write_short(&mut self, n: u16) -> 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_byte_array(&mut self, bytes: &[u8]) -> Result<(), std::io::Error>;
|
||||||
fn write_int(&mut self, n: i32) -> 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]
|
#[async_trait]
|
||||||
impl Writable for Vec<u8> {
|
impl Writable for Vec<u8> {
|
||||||
fn write_list<F, T>(&mut self, list: Vec<T>, writer: F) -> Result<(), std::io::Error>
|
fn write_list<F, T>(&mut self, list: &Vec<T>, writer: F) -> Result<(), std::io::Error>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self, T) -> Result<(), std::io::Error> + Copy,
|
F: FnOnce(&mut Self, &T) -> Result<(), std::io::Error> + Copy,
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.write_varint(list.len() as i32)?;
|
self.write_varint(list.len() as i32)?;
|
||||||
|
@ -53,7 +56,7 @@ impl Writable for Vec<u8> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error> {
|
fn write_int_id_list(&mut self, list: Vec<i32>) -> Result<(), std::io::Error> {
|
||||||
self.write_list(list, Self::write_varint)
|
self.write_list(&list, |buf, n| buf.write_varint(*n))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_map<KF, VF, KT, VT>(
|
fn write_map<KF, VF, KT, VT>(
|
||||||
|
@ -128,6 +131,15 @@ impl Writable for Vec<u8> {
|
||||||
fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> {
|
fn write_int(&mut self, n: i32) -> Result<(), std::io::Error> {
|
||||||
WriteBytesExt::write_i32::<BigEndian>(self, n)
|
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()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -142,6 +154,8 @@ pub trait Readable {
|
||||||
async fn read_utf_with_len(&mut self, max_length: u32) -> 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_byte(&mut self) -> Result<u8, String>;
|
||||||
async fn read_int(&mut self) -> Result<i32, 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_trait]
|
#[async_trait]
|
||||||
|
@ -261,6 +275,19 @@ where
|
||||||
Err(_) => Err("Error reading int".to_string()),
|
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> {
|
||||||
|
self.peek();
|
||||||
|
Ok(azalea_nbt::Tag::read(self).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -301,7 +328,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list() {
|
async fn test_list() {
|
||||||
let mut buf = Vec::new();
|
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();
|
.unwrap();
|
||||||
|
|
||||||
// there's no read_list because idk how to do it in rust
|
// there's no read_list because idk how to do it in rust
|
||||||
|
@ -319,7 +346,8 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_int_id_list() {
|
async fn test_int_id_list() {
|
||||||
let mut buf = Vec::new();
|
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));
|
let mut buf = BufReader::new(Cursor::new(buf));
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use super::GamePacket;
|
use super::GamePacket;
|
||||||
use crate::mc_buf::{Readable, Writable};
|
use crate::mc_buf::{Readable, Writable};
|
||||||
use azalea_core::{game_type::GameType, resource_location::ResourceLocation};
|
use azalea_core::{game_type::GameType, resource_location::ResourceLocation};
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
#[derive(Hash, Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClientboundLoginPacket {
|
pub struct ClientboundLoginPacket {
|
||||||
// private final int playerId;
|
// private final int playerId;
|
||||||
// private final boolean hardcore;
|
// private final boolean hardcore;
|
||||||
|
@ -27,7 +26,17 @@ pub struct ClientboundLoginPacket {
|
||||||
pub game_type: GameType,
|
pub game_type: GameType,
|
||||||
pub previous_game_type: Option<GameType>,
|
pub previous_game_type: Option<GameType>,
|
||||||
pub levels: Vec<ResourceLocation>,
|
pub levels: Vec<ResourceLocation>,
|
||||||
// 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 {
|
impl ClientboundLoginPacket {
|
||||||
|
@ -35,10 +44,19 @@ impl ClientboundLoginPacket {
|
||||||
GamePacket::ClientboundLoginPacket(self)
|
GamePacket::ClientboundLoginPacket(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&self, buf: &mut Vec<u8>) {
|
pub fn write(&self, buf: &mut Vec<u8>) -> Result<(), std::io::Error> {
|
||||||
buf.write_int(self.player_id);
|
buf.write_int(self.player_id)?;
|
||||||
// buf.write_bool(self.hardcore);
|
buf.write_boolean(self.hardcore)?;
|
||||||
// buf.write_byte(self.game_type.
|
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<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
|
pub async fn read<T: tokio::io::AsyncRead + std::marker::Unpin + std::marker::Send>(
|
||||||
|
|
Loading…
Add table
Reference in a new issue