Merge branch 'main' into chunk-decoding

This commit is contained in:
mat 2022-05-08 18:55:49 -05:00
commit ac392542ce
63 changed files with 6797 additions and 543 deletions

6
.gitignore vendored Executable file → Normal file
View file

@ -6,3 +6,9 @@ perf.data.old
# TODO: remove this after chunk-decoding is merged
/login.txt
code-generator/Burger
code-generator/client.jar
code-generator/burger.json
__pycache__
*.tmp

44
Cargo.lock generated
View file

@ -86,6 +86,7 @@ version = "0.1.0"
name = "azalea-chat"
version = "0.1.0"
dependencies = [
"azalea-language",
"lazy_static",
"serde",
"serde_json",
@ -124,6 +125,15 @@ dependencies = [
"sha-1",
]
[[package]]
name = "azalea-language"
version = "0.1.0"
dependencies = [
"lazy_static",
"serde",
"serde_json",
]
[[package]]
name = "azalea-nbt"
version = "0.1.0"
@ -397,7 +407,7 @@ checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa",
"itoa 0.4.8",
"ryu",
"serde",
]
@ -631,6 +641,12 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "js-sys"
version = "0.3.55"
@ -1049,9 +1065,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [
"regex-syntax",
]
@ -1127,9 +1143,9 @@ checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
[[package]]
name = "serde"
version = "1.0.130"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [
"serde_derive",
]
@ -1146,9 +1162,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.130"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [
"proc-macro2",
"quote",
@ -1157,11 +1173,11 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.72"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
dependencies = [
"itoa",
"itoa 1.0.1",
"ryu",
"serde",
]
@ -1224,9 +1240,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.82"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
dependencies = [
"proc-macro2",
"quote",
@ -1289,9 +1305,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.18.0"
version = "1.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b"
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
dependencies = [
"bytes",
"libc",

12
Cargo.toml Executable file → Normal file
View file

@ -1,5 +1,4 @@
[workspace]
members = [
"bot",
"azalea-client",
@ -11,7 +10,18 @@ members = [
"azalea-brigadier",
"azalea-crypto",
"azalea-world",
"azalea-language",
]
[profile.release]
debug = true
# decoding packets takes forever if we don't do this
[profile.dev.package.azalea-crypto]
opt-level = 3
[profile.dev.package.cipher]
opt-level = 3
[profile.dev.package.cfb8]
opt-level = 3
[profile.dev.package.aes]
opt-level = 3

View file

@ -6,6 +6,7 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
azalea-language = {path = "../azalea-language"}
lazy_static = "1.4.0"
serde = "^1.0.130"
serde_json = "^1.0.72"

View file

@ -63,14 +63,15 @@ impl Component {
for component in self.clone().into_iter() {
let component_text = match &component {
Self::Text(c) => &c.text,
Self::Translatable(c) => &c.key,
Self::Text(c) => c.text.to_string(),
Self::Translatable(c) => c.to_string(),
};
let component_style = &component.get_base().style;
let ansi_text = running_style.compare_ansi(component_style, default_style);
built_string.push_str(&ansi_text);
built_string.push_str(component_text);
built_string.push_str(&component_text);
running_style.apply(component_style);
}
@ -123,7 +124,12 @@ impl<'de> Deserialize<'de> for Component {
let text = json.get("text").unwrap().as_str().unwrap_or("").to_string();
component = Component::Text(TextComponent::new(text));
} else if json.get("translate").is_some() {
let translate = json.get("translate").unwrap().to_string();
let translate = json
.get("translate")
.unwrap()
.as_str()
.ok_or_else(|| de::Error::custom("\"translate\" must be a string"))?
.into();
if json.get("with").is_some() {
let with = json.get("with").unwrap().as_array().unwrap();
let mut with_array = Vec::with_capacity(with.len());
@ -136,7 +142,7 @@ impl<'de> Deserialize<'de> for Component {
&& text_component.base.style.is_empty()
{
with_array.push(StringOrComponent::String(text_component.text));
break;
continue;
}
}
with_array.push(StringOrComponent::Component(

View file

@ -1,3 +1,5 @@
use std::fmt::{self, Formatter};
use crate::{base_component::BaseComponent, component::Component};
#[derive(Clone, Debug)]
@ -21,4 +23,157 @@ impl TranslatableComponent {
args,
}
}
pub fn read(&self) -> Result<String, fmt::Error> {
let template = azalea_language::get(&self.key).unwrap_or_else(|| &self.key);
// decode the % things
let mut result = String::new();
let mut i = 0;
let mut matched = 0;
// this code is ugly but it works
while i < template.len() {
if template.chars().nth(i).unwrap() == '%' {
let char_after = match template.chars().nth(i + 1) {
Some(c) => c,
None => {
result.push(template.chars().nth(i).unwrap());
break;
}
};
i += 1;
match char_after {
'%' => {
result.push('%');
}
's' => {
result.push_str(
&self
.args
.get(matched)
.unwrap_or(&StringOrComponent::String("".to_string()))
.to_string(),
);
matched += 1;
}
_ => {
// check if the char is a number
if let Some(d) = char_after.to_digit(10) {
// make sure the next two chars are $s
if let Some('$') = template.chars().nth(i + 1) {
if let Some('s') = template.chars().nth(i + 2) {
i += 2;
result.push_str(
&self
.args
.get((d - 1) as usize)
.unwrap_or(&StringOrComponent::String("".to_string()))
.to_string(),
);
} else {
return Err(fmt::Error);
}
} else {
return Err(fmt::Error);
}
} else {
i -= 1;
result.push('%');
}
}
}
} else {
result.push(template.chars().nth(i).unwrap());
}
i += 1
}
Ok(result.to_string())
}
}
impl fmt::Display for TranslatableComponent {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "{}", self.read()?)
}
}
impl fmt::Display for StringOrComponent {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
match self {
StringOrComponent::String(s) => write!(f, "{}", s),
StringOrComponent::Component(c) => write!(f, "{}", c.to_ansi(None)),
}
}
}
// tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_none() {
let c = TranslatableComponent::new("translation.test.none".to_string(), vec![]);
assert_eq!(c.read(), Ok("Hello, world!".to_string()));
}
#[test]
fn test_complex() {
let c = TranslatableComponent::new(
"translation.test.complex".to_string(),
vec![
StringOrComponent::String("a".to_string()),
StringOrComponent::String("b".to_string()),
StringOrComponent::String("c".to_string()),
StringOrComponent::String("d".to_string()),
],
);
// so true mojang
assert_eq!(
c.read(),
Ok("Prefix, ab again b and a lastly c and also a again!".to_string())
);
}
#[test]
fn test_escape() {
let c = TranslatableComponent::new(
"translation.test.escape".to_string(),
vec![
StringOrComponent::String("a".to_string()),
StringOrComponent::String("b".to_string()),
StringOrComponent::String("c".to_string()),
StringOrComponent::String("d".to_string()),
],
);
assert_eq!(c.read(), Ok("%s %a %%s %%b".to_string()));
}
#[test]
fn test_invalid() {
let c = TranslatableComponent::new(
"translation.test.invalid".to_string(),
vec![
StringOrComponent::String("a".to_string()),
StringOrComponent::String("b".to_string()),
StringOrComponent::String("c".to_string()),
StringOrComponent::String("d".to_string()),
],
);
assert_eq!(c.read(), Ok("hi %".to_string()));
}
#[test]
fn test_invalid2() {
let c = TranslatableComponent::new(
"translation.test.invalid2".to_string(),
vec![
StringOrComponent::String("a".to_string()),
StringOrComponent::String("b".to_string()),
StringOrComponent::String("c".to_string()),
StringOrComponent::String("d".to_string()),
],
);
assert_eq!(c.read(), Ok("hi % s".to_string()));
}
}

View file

@ -3,7 +3,11 @@ use azalea_core::{resource_location::ResourceLocation, ChunkPos};
use azalea_protocol::{
connect::{GameConnection, HandshakeConnection},
packets::{
game::{serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, GamePacket},
game::{
clientbound_chat_packet::ClientboundChatPacket,
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
serverbound_keep_alive_packet::ServerboundKeepAlivePacket, GamePacket,
},
handshake::client_intention_packet::ClientIntentionPacket,
login::{
serverbound_hello_packet::ServerboundHelloPacket,
@ -42,6 +46,7 @@ pub struct Client {
#[derive(Debug, Clone)]
pub enum Event {
Login,
Chat(ClientboundChatPacket),
}
/// Whether we should ignore errors when decoding packets.
@ -239,7 +244,7 @@ impl Client {
println!("Got update recipes packet");
}
GamePacket::ClientboundEntityEventPacket(p) => {
println!("Got entity event packet {:?}", p);
// println!("Got entity event packet {:?}", p);
}
GamePacket::ClientboundRecipePacket(_p) => {
println!("Got recipe packet");
@ -285,20 +290,84 @@ impl Client {
println!("Got add entity packet {:?}", p);
}
GamePacket::ClientboundSetEntityDataPacket(p) => {
println!("Got set entity data packet {:?}", p);
// println!("Got set entity data packet {:?}", p);
}
GamePacket::ClientboundUpdateAttributesPacket(p) => {
println!("Got update attributes packet {:?}", p);
// println!("Got update attributes packet {:?}", p);
}
GamePacket::ClientboundEntityVelocityPacket(p) => {
println!("Got entity velocity packet {:?}", p);
// println!("Got entity velocity packet {:?}", p);
}
GamePacket::ClientboundSetEntityLinkPacket(p) => {
println!("Got set entity link packet {:?}", p);
}
GamePacket::ClientboundAddPlayerPacket(p) => {
println!("Got add player packet {:?}", p);
}
GamePacket::ClientboundInitializeBorderPacket(p) => {
println!("Got initialize border packet {:?}", p);
}
GamePacket::ClientboundSetTimePacket(p) => {
println!("Got set time packet {:?}", p);
}
GamePacket::ClientboundSetDefaultSpawnPositionPacket(p) => {
println!("Got set default spawn position packet {:?}", p);
}
GamePacket::ClientboundContainerSetContentPacket(p) => {
println!("Got container set content packet {:?}", p);
}
GamePacket::ClientboundSetHealthPacket(p) => {
println!("Got set health packet {:?}", p);
}
GamePacket::ClientboundSetExperiencePacket(p) => {
println!("Got set experience packet {:?}", p);
}
GamePacket::ClientboundTeleportEntityPacket(p) => {
// println!("Got teleport entity packet {:?}", p);
}
GamePacket::ClientboundUpdateAdvancementsPacket(p) => {
println!("Got update advancements packet {:?}", p);
}
GamePacket::ClientboundRotateHeadPacket(p) => {
// println!("Got rotate head packet {:?}", p);
}
GamePacket::ClientboundMoveEntityPosPacket(p) => {
// println!("Got move entity pos packet {:?}", p);
}
GamePacket::ClientboundMoveEntityPosRotPacket(p) => {
// println!("Got move entity pos rot packet {:?}", p);
}
GamePacket::ClientboundMoveEntityRotPacket(p) => {
println!("Got move entity rot packet {:?}", p);
}
GamePacket::ClientboundKeepAlivePacket(p) => {
println!("Got keep alive packet {:?}", p);
conn.lock()
.await
.write(ServerboundKeepAlivePacket { id: p.id }.get())
.await;
}
GamePacket::ClientboundRemoveEntitiesPacket(p) => {
println!("Got remove entities packet {:?}", p);
}
GamePacket::ClientboundChatPacket(p) => {
println!("Got chat packet {:?}", p);
tx.send(Event::Chat(p.clone())).unwrap();
}
GamePacket::ClientboundSoundPacket(p) => {
println!("Got sound packet {:?}", p);
}
GamePacket::ClientboundLevelEventPacket(p) => {
println!("Got level event packet {:?}", p);
}
GamePacket::ClientboundBlockUpdatePacket(p) => {
println!("Got block update packet {:?}", p);
}
GamePacket::ClientboundAnimatePacket(p) => {
println!("Got animate packet {:?}", p);
}
_ => panic!("Unexpected packet {:?}", packet),
}
println!();
}
pub async fn next(&mut self) -> Option<Event> {

View file

@ -1,4 +1,4 @@
// TODO: have an azalea-inventory crate and put this there
// TODO: have an azalea-inventory or azalea-container crate and put this there
#[derive(Debug, Clone)]
pub enum Slot {
@ -9,8 +9,6 @@ pub enum Slot {
#[derive(Debug, Clone)]
pub struct SlotData {
pub id: i32,
// TODO: is this really a u8? is it a i8? is it a varint?
// wiki.vg says it's a "byte"
pub count: u8,
pub nbt: azalea_nbt::Tag,
}

View file

@ -55,7 +55,6 @@ pub fn encrypt(public_key: &[u8], nonce: &[u8]) -> Result<EncryptResult, String>
})
}
// TODO: update the aes and cfb8 crates
pub type Aes128CfbEnc = cfb8::Encryptor<Aes128>;
pub type Aes128CfbDec = cfb8::Decryptor<Aes128>;

View file

@ -0,0 +1,12 @@
[package]
edition = "2021"
name = "azalea-language"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lazy_static = "1.4.0"
serde = "1.0.137"
serde_json = "1.0.81"
# tokio = {version = "1.18.2", features = ["fs"]}

View file

@ -0,0 +1,4 @@
# Azalea Assets
Translate Minecraft strings from their id.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,38 @@
use lazy_static::lazy_static;
use std::collections::HashMap;
// use tokio::fs::File;
// pub struct Language {
// pub storage: HashMap<String, String>,
// }
// impl Language {
// pub async fn load() -> Self {
// // TODO: download from mojang's servers and cache somewhere
// let mut storage = HashMap::new();
// let mut file = File::open("en_us.json").unwrap();
// let mut contents = String::new();
// file.read_to_string(&mut contents).unwrap();
// let en_us: HashMap<String, String> = serde_json::from_str(&contents).unwrap();
// Language { storage: en_us }
// }
// pub fn get(&self, key: &str) -> Option<&str> {
// self.storage.get(key)
// }
// }
// yeah i just decided to do this because otherwise we would have to have a
// Language object that we passed around everywhere which is not convenient
// The code above is kept in case I come up with a better solution
lazy_static! {
pub static ref STORAGE: HashMap<String, String> =
serde_json::from_str(include_str!("en_us.json")).unwrap();
}
pub fn get(key: &str) -> Option<&str> {
STORAGE.get(key).map(|s| s.as_str())
}

15
azalea-protocol/README.md Executable file → Normal file
View file

@ -7,3 +7,18 @@ The goal is to only support the latest Minecraft version in order to ease develo
This is not yet complete, search for `TODO` in the code for things that need to be done.
Unfortunately, using azalea-protocol requires Rust nightly because [specialization](https://github.com/rust-lang/rust/issues/31844) is not stable yet. Use `rustup default nightly` to enable it.
## Adding a new packet
Adding new packets is usually pretty easy, but you'll want to have Minecraft's decompiled source code which you can obtain with tools such as [DecompilerMC](https://github.com/hube12/DecompilerMC).
1. Find the packet in Minecraft's source code. Minecraft's packets are in the `net/minecraft/network/protocol/<state>` directory. The state for your packet is usually `game`.
2. Add a new file in the [`packets/<state>`](./src/packets/game) directory with the snake_cased version of the name Minecraft uses.
3. Copy the code from a similar packet and change the struct name.
4. Add the fields from Minecraft's source code from either the read or write methods.
If it's a `varint`, use `#[var] pub <name>: i32` (or u32 if it makes more sense).
If it's a `varlong`, use `#[var] pub <name>: i64` (or u64).
If it's a byte, use i8 or u8.
Etc.. You can look at [wiki.vg](https://wiki.vg/Protocol) if you're not sure about how a packet is structured, but be aware that wiki.vg uses different names for most things.
5. Add the packet to the `mod.rs` file in the same directory. You will have to look at [wiki.vg](https://wiki.vg/Protocol) to determine the packet id here.
6. That's it! Format your code, submit a pull request, and wait for it to be reviewed.

View file

@ -23,9 +23,9 @@ fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenSt
// 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")) {
if f.attrs.iter().any(|a| a.path.is_ident("var")) {
quote! {
let #field_name = crate::mc_buf::McBufVarintReadable::varint_read_into(buf)?;
let #field_name = crate::mc_buf::McBufVarReadable::var_read_into(buf)?;
}
} else {
quote! {
@ -72,7 +72,7 @@ fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenSt
impl crate::mc_buf::McBufReadable for #ident {
fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String>
{
let id = buf.read_varint()?;
let id = crate::mc_buf::McBufVarReadable::var_read_into(buf)?;
match id {
#match_contents
_ => Err(format!("Unknown enum variant {}", id)),
@ -102,9 +102,9 @@ fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::TokenSt
// 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")) {
if f.attrs.iter().any(|attr| attr.path.is_ident("var")) {
quote! {
crate::mc_buf::McBufVarintWritable::varint_write_into(&self.#field_name, buf)?;
crate::mc_buf::McBufVarWritable::var_write_into(&self.#field_name, buf)?;
}
} else {
quote! {
@ -143,14 +143,14 @@ fn create_impl_mcbufwritable(ident: &Ident, data: &Data) -> proc_macro2::TokenSt
}
}
#[proc_macro_derive(McBufReadable, attributes(varint))]
#[proc_macro_derive(McBufReadable, attributes(var))]
pub fn derive_mcbufreadable(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
create_impl_mcbufreadable(&ident, &data).into()
}
#[proc_macro_derive(McBufWritable, attributes(varint))]
#[proc_macro_derive(McBufWritable, attributes(var))]
pub fn derive_mcbufwritable(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
@ -198,22 +198,22 @@ fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> Toke
contents.into()
}
#[proc_macro_derive(GamePacket, attributes(varint))]
#[proc_macro_derive(GamePacket, attributes(var))]
pub fn derive_game_packet(input: TokenStream) -> TokenStream {
as_packet_derive(input, quote! {crate::packets::game::GamePacket})
}
#[proc_macro_derive(HandshakePacket, attributes(varint))]
#[proc_macro_derive(HandshakePacket, attributes(var))]
pub fn derive_handshake_packet(input: TokenStream) -> TokenStream {
as_packet_derive(input, quote! {crate::packets::handshake::HandshakePacket})
}
#[proc_macro_derive(LoginPacket, attributes(varint))]
#[proc_macro_derive(LoginPacket, attributes(var))]
pub fn derive_login_packet(input: TokenStream) -> TokenStream {
as_packet_derive(input, quote! {crate::packets::login::LoginPacket})
}
#[proc_macro_derive(StatusPacket, attributes(varint))]
#[proc_macro_derive(StatusPacket, attributes(var))]
pub fn derive_status_packet(input: TokenStream) -> TokenStream {
as_packet_derive(input, quote! {crate::packets::status::StatusPacket})
}

View file

@ -10,6 +10,7 @@ use crate::ServerIpAddress;
use azalea_crypto::{Aes128CfbDec, Aes128CfbEnc};
use tokio::net::TcpStream;
#[derive(Debug, Clone, Copy)]
pub enum PacketFlow {
ClientToServer,
ServerToClient,

View file

@ -0,0 +1,435 @@
use crate::mc_buf::read::{McBufReadable, Readable};
use crate::mc_buf::write::{McBufWritable, Writable};
use azalea_chat::component::Component;
use azalea_core::{BlockPos, Direction, Slot};
use packet_macros::{McBufReadable, McBufWritable};
use std::io::{Read, Write};
use std::ops::Deref;
use uuid::Uuid;
/// A Vec<u8> that isn't prefixed by a VarInt with the size.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnsizedByteArray(Vec<u8>);
impl Deref for UnsizedByteArray {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Vec<u8>> for UnsizedByteArray {
fn from(vec: Vec<u8>) -> Self {
Self(vec)
}
}
impl From<&str> for UnsizedByteArray {
fn from(s: &str) -> Self {
Self(s.as_bytes().to_vec())
}
}
/// Represents Java's BitSet, a list of bits.
#[derive(Debug, Clone, PartialEq, Eq, Hash, McBufReadable, McBufWritable)]
pub struct BitSet {
data: Vec<u64>,
}
// the Index trait requires us to return a reference, but we can't do that
impl BitSet {
pub fn index(&self, index: usize) -> bool {
(self.data[index / 64] & (1u64 << (index % 64))) != 0
}
}
pub type EntityMetadata = Vec<EntityDataItem>;
#[derive(Clone, Debug)]
pub struct EntityDataItem {
// we can't identify what the index is for here because we don't know the
// entity type
pub index: u8,
pub value: EntityDataValue,
}
impl McBufReadable for Vec<EntityDataItem> {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let mut metadata = Vec::new();
loop {
let index = buf.read_byte()?;
if index == 0xff {
break;
}
let value = EntityDataValue::read_into(buf)?;
metadata.push(EntityDataItem { index, value });
}
Ok(metadata)
}
}
impl McBufWritable for Vec<EntityDataItem> {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
for item in self {
buf.write_byte(item.index)?;
item.value.write_into(buf)?;
}
buf.write_byte(0xff)?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub enum EntityDataValue {
Byte(u8),
// varint
Int(i32),
Float(f32),
String(String),
Component(Component),
OptionalComponent(Option<Component>),
ItemStack(Slot),
Boolean(bool),
Rotations { x: f32, y: f32, z: f32 },
BlockPos(BlockPos),
OptionalBlockPos(Option<BlockPos>),
Direction(Direction),
OptionalUuid(Option<Uuid>),
// 0 for absent (implies air); otherwise, a block state ID as per the global palette
// this is a varint
OptionalBlockState(Option<i32>),
CompoundTag(azalea_nbt::Tag),
Particle(Particle),
VillagerData(VillagerData),
// 0 for absent; 1 + actual value otherwise. Used for entity IDs.
OptionalUnsignedInt(Option<u32>),
Pose(Pose),
}
impl McBufReadable for EntityDataValue {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let type_ = buf.read_varint()?;
Ok(match type_ {
0 => EntityDataValue::Byte(buf.read_byte()?),
1 => EntityDataValue::Int(buf.read_varint()?),
2 => EntityDataValue::Float(buf.read_float()?),
3 => EntityDataValue::String(buf.read_utf()?),
4 => EntityDataValue::Component(Component::read_into(buf)?),
5 => EntityDataValue::OptionalComponent(Option::<Component>::read_into(buf)?),
6 => EntityDataValue::ItemStack(Slot::read_into(buf)?),
7 => EntityDataValue::Boolean(buf.read_boolean()?),
8 => EntityDataValue::Rotations {
x: buf.read_float()?,
y: buf.read_float()?,
z: buf.read_float()?,
},
9 => EntityDataValue::BlockPos(BlockPos::read_into(buf)?),
10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_into(buf)?),
11 => EntityDataValue::Direction(Direction::read_into(buf)?),
12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_into(buf)?),
13 => EntityDataValue::OptionalBlockState({
let val = i32::read_into(buf)?;
if val == 0 {
None
} else {
Some(val)
}
}),
14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_into(buf)?),
15 => EntityDataValue::Particle(Particle::read_into(buf)?),
16 => EntityDataValue::VillagerData(VillagerData::read_into(buf)?),
17 => EntityDataValue::OptionalUnsignedInt({
let val = buf.read_varint()?;
if val == 0 {
None
} else {
Some((val - 1) as u32)
}
}),
18 => EntityDataValue::Pose(Pose::read_into(buf)?),
_ => return Err(format!("Unknown entity data type: {}", type_)),
})
}
}
impl McBufWritable for EntityDataValue {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
todo!();
}
}
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
pub enum Pose {
Standing = 0,
FallFlying = 1,
Sleeping = 2,
Swimming = 3,
SpinAttack = 4,
Sneaking = 5,
LongJumping = 6,
Dying = 7,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct VillagerData {
#[var]
type_: u32,
#[var]
profession: u32,
#[var]
level: u32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct Particle {
#[var]
pub id: i32,
pub data: ParticleData,
}
#[derive(Clone, Debug)]
pub enum ParticleData {
AmbientEntityEffect,
AngryVillager,
Block(BlockParticle),
BlockMarker(BlockParticle),
Bubble,
Cloud,
Crit,
DamageIndicator,
DragonBreath,
DrippingLava,
FallingLava,
LandingLava,
DrippingWater,
FallingWater,
Dust(DustParticle),
DustColorTransition(DustColorTransitionParticle),
Effect,
ElderGuardian,
EnchantedHit,
Enchant,
EndRod,
EntityEffect,
ExplosionEmitter,
Explosion,
FallingDust(BlockParticle),
Firework,
Fishing,
Flame,
SoulFireFlame,
Soul,
Flash,
HappyVillager,
Composter,
Heart,
InstantEffect,
Item(ItemParticle),
Vibration(VibrationParticle),
ItemSlime,
ItemSnowball,
LargeSmoke,
Lava,
Mycelium,
Note,
Poof,
Portal,
Rain,
Smoke,
Sneeze,
Spit,
SquidInk,
SweepAttack,
TotemOfUndying,
Underwater,
Splash,
Witch,
BubblePop,
CurrentDown,
BubbleColumnUp,
Nautilus,
Dolphin,
CampfireCozySmoke,
CampfireSignalSmoke,
DrippingHoney,
FallingHoney,
LandingHoney,
FallingNectar,
FallingSporeBlossom,
Ash,
CrimsonSpore,
WarpedSpore,
SporeBlossomAir,
DrippingObsidianTear,
FallingObsidianTear,
LandingObsidianTear,
ReversePortal,
WhiteAsh,
SmallFlame,
Snowflake,
DrippingDripstoneLava,
FallingDripstoneLava,
DrippingDripstoneWater,
FallingDripstoneWater,
GlowSquidInk,
Glow,
WaxOn,
WaxOff,
ElectricSpark,
Scrape,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct BlockParticle {
#[var]
pub block_state: i32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct DustParticle {
/// Red value, 0-1
pub red: f32,
/// Green value, 0-1
pub green: f32,
/// Blue value, 0-1
pub blue: f32,
/// The scale, will be clamped between 0.01 and 4.
pub scale: f32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct DustColorTransitionParticle {
/// Red value, 0-1
pub from_red: f32,
/// Green value, 0-1
pub from_green: f32,
/// Blue value, 0-1
pub from_blue: f32,
/// The scale, will be clamped between 0.01 and 4.
pub scale: f32,
/// Red value, 0-1
pub to_red: f32,
/// Green value, 0-1
pub to_green: f32,
/// Blue value, 0-1
pub to_blue: f32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct ItemParticle {
pub item: Slot,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct VibrationParticle {
pub origin: BlockPos,
pub position_type: String,
pub block_position: BlockPos,
#[var]
pub entity_id: u32,
#[var]
pub ticks: u32,
}
impl McBufReadable for ParticleData {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let id = buf.read_varint()?;
Ok(match id {
0 => ParticleData::AmbientEntityEffect,
1 => ParticleData::AngryVillager,
2 => ParticleData::Block(BlockParticle::read_into(buf)?),
3 => ParticleData::BlockMarker(BlockParticle::read_into(buf)?),
4 => ParticleData::Bubble,
5 => ParticleData::Cloud,
6 => ParticleData::Crit,
7 => ParticleData::DamageIndicator,
8 => ParticleData::DragonBreath,
9 => ParticleData::DrippingLava,
10 => ParticleData::FallingLava,
11 => ParticleData::LandingLava,
12 => ParticleData::DrippingWater,
13 => ParticleData::FallingWater,
14 => ParticleData::Dust(DustParticle::read_into(buf)?),
15 => ParticleData::DustColorTransition(DustColorTransitionParticle::read_into(buf)?),
16 => ParticleData::Effect,
17 => ParticleData::ElderGuardian,
18 => ParticleData::EnchantedHit,
19 => ParticleData::Enchant,
20 => ParticleData::EndRod,
21 => ParticleData::EntityEffect,
22 => ParticleData::ExplosionEmitter,
23 => ParticleData::Explosion,
24 => ParticleData::FallingDust(BlockParticle::read_into(buf)?),
25 => ParticleData::Firework,
26 => ParticleData::Fishing,
27 => ParticleData::Flame,
28 => ParticleData::SoulFireFlame,
29 => ParticleData::Soul,
30 => ParticleData::Flash,
31 => ParticleData::HappyVillager,
32 => ParticleData::Composter,
33 => ParticleData::Heart,
34 => ParticleData::InstantEffect,
35 => ParticleData::Item(ItemParticle::read_into(buf)?),
36 => ParticleData::Vibration(VibrationParticle::read_into(buf)?),
37 => ParticleData::ItemSlime,
38 => ParticleData::ItemSnowball,
39 => ParticleData::LargeSmoke,
40 => ParticleData::Lava,
41 => ParticleData::Mycelium,
42 => ParticleData::Note,
43 => ParticleData::Poof,
44 => ParticleData::Portal,
45 => ParticleData::Rain,
46 => ParticleData::Smoke,
47 => ParticleData::Sneeze,
48 => ParticleData::Spit,
49 => ParticleData::SquidInk,
50 => ParticleData::SweepAttack,
51 => ParticleData::TotemOfUndying,
52 => ParticleData::Underwater,
53 => ParticleData::Splash,
54 => ParticleData::Witch,
55 => ParticleData::BubblePop,
56 => ParticleData::CurrentDown,
57 => ParticleData::BubbleColumnUp,
58 => ParticleData::Nautilus,
59 => ParticleData::Dolphin,
60 => ParticleData::CampfireCozySmoke,
61 => ParticleData::CampfireSignalSmoke,
62 => ParticleData::DrippingHoney,
63 => ParticleData::FallingHoney,
64 => ParticleData::LandingHoney,
65 => ParticleData::FallingNectar,
66 => ParticleData::FallingSporeBlossom,
67 => ParticleData::Ash,
68 => ParticleData::CrimsonSpore,
69 => ParticleData::WarpedSpore,
70 => ParticleData::SporeBlossomAir,
71 => ParticleData::DrippingObsidianTear,
72 => ParticleData::FallingObsidianTear,
73 => ParticleData::LandingObsidianTear,
74 => ParticleData::ReversePortal,
75 => ParticleData::WhiteAsh,
76 => ParticleData::SmallFlame,
77 => ParticleData::Snowflake,
78 => ParticleData::DrippingDripstoneLava,
79 => ParticleData::FallingDripstoneLava,
80 => ParticleData::DrippingDripstoneWater,
81 => ParticleData::FallingDripstoneWater,
82 => ParticleData::GlowSquidInk,
83 => ParticleData::Glow,
84 => ParticleData::WaxOn,
85 => ParticleData::WaxOff,
86 => ParticleData::ElectricSpark,
87 => ParticleData::Scrape,
_ => return Err(format!("Unknown particle id: {}", id)),
})
}
}
impl McBufWritable for ParticleData {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
todo!()
}
}

43
azalea-protocol/src/mc_buf/mod.rs Executable file → Normal file
View file

@ -1,12 +1,14 @@
//! Utilities for reading and writing for the Minecraft protocol
mod definitions;
mod read;
mod write;
pub use definitions::{BitSet, EntityMetadata, UnsizedByteArray};
use packet_macros::{McBufReadable, McBufWritable};
pub use read::{read_varint_async, McBufReadable, McBufVarintReadable, Readable};
pub use read::{read_varint_async, McBufReadable, McBufVarReadable, Readable};
use std::ops::Deref;
pub use write::{McBufVarintWritable, McBufWritable, Writable};
pub use write::{McBufVarWritable, McBufWritable, Writable};
// const DEFAULT_NBT_QUOTA: u32 = 2097152;
const MAX_STRING_LENGTH: u16 = 32767;
@ -16,43 +18,6 @@ const MAX_STRING_LENGTH: u16 = 32767;
// TODO: have a definitions.rs in mc_buf that contains UnsizedByteArray and BitSet
/// A Vec<u8> that isn't prefixed by a VarInt with the size.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnsizedByteArray(Vec<u8>);
impl Deref for UnsizedByteArray {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Vec<u8>> for UnsizedByteArray {
fn from(vec: Vec<u8>) -> Self {
Self(vec)
}
}
impl From<&str> for UnsizedByteArray {
fn from(s: &str) -> Self {
Self(s.as_bytes().to_vec())
}
}
/// Represents Java's BitSet, a list of bits.
#[derive(Debug, Clone, PartialEq, Eq, Hash, McBufReadable, McBufWritable)]
pub struct BitSet {
data: Vec<u64>,
}
// the Index trait requires us to return a reference, but we can't do that
impl BitSet {
pub fn index(&self, index: usize) -> bool {
(self.data[index / 64] & (1u64 << (index % 64))) != 0
}
}
#[cfg(test)]
mod tests {
use super::*;

68
azalea-protocol/src/mc_buf/read.rs Executable file → Normal file
View file

@ -6,7 +6,7 @@ use azalea_core::{
};
use byteorder::{ReadBytesExt, BE};
use serde::Deserialize;
use std::io::Read;
use std::{collections::HashMap, hash::Hash, io::Read};
use tokio::io::{AsyncRead, AsyncReadExt};
use uuid::Uuid;
@ -236,11 +236,11 @@ where
fn read_into(buf: &mut impl Read) -> Result<Self, String>;
}
pub trait McBufVarintReadable
pub trait McBufVarReadable
where
Self: Sized,
{
fn varint_read_into(buf: &mut impl Read) -> Result<Self, String>;
fn var_read_into(buf: &mut impl Read) -> Result<Self, String>;
}
impl McBufReadable for i32 {
@ -249,26 +249,37 @@ impl McBufReadable for i32 {
}
}
impl McBufVarintReadable for i32 {
fn varint_read_into(buf: &mut impl Read) -> Result<Self, String> {
impl McBufVarReadable for i32 {
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_varint()
}
}
impl<T: McBufVarintReadable> McBufVarintReadable for Vec<T> {
fn varint_read_into(buf: &mut impl Read) -> Result<Self, String> {
let length = u32::varint_read_into(buf)?;
let mut vec = Vec::with_capacity(length as usize);
for _ in 0..length {
vec.push(T::varint_read_into(buf)?);
impl McBufVarReadable for i64 {
// fast varints modified from https://github.com/luojia65/mc-varint/blob/master/src/lib.rs#L54
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
let mut buffer = [0];
let mut ans = 0;
for i in 0..8 {
buf.read_exact(&mut buffer)
.map_err(|_| "Invalid VarLong".to_string())?;
ans |= ((buffer[0] & 0b0111_1111) as i64) << 7 * i;
if buffer[0] & 0b1000_0000 == 0 {
break;
}
Ok(vec)
}
Ok(ans)
}
}
impl McBufVarReadable for u64 {
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
i64::var_read_into(buf).map(|i| i as u64)
}
}
impl McBufReadable for UnsizedByteArray {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
Ok(UnsizedByteArray(buf.read_bytes()?))
Ok(buf.read_bytes()?.into())
}
}
@ -283,6 +294,17 @@ impl<T: McBufReadable + Send> McBufReadable for Vec<T> {
}
}
impl<K: McBufReadable + Send + Eq + Hash, V: McBufReadable + Send> McBufReadable for HashMap<K, V> {
default fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let length = buf.read_varint()? as usize;
let mut contents = HashMap::with_capacity(length);
for _ in 0..length {
contents.insert(K::read_into(buf)?, V::read_into(buf)?);
}
Ok(contents)
}
}
impl McBufReadable for Vec<u8> {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_byte_array()
@ -311,8 +333,8 @@ impl McBufReadable for u32 {
}
// u32 varint
impl McBufVarintReadable for u32 {
fn varint_read_into(buf: &mut impl Read) -> Result<Self, String> {
impl McBufVarReadable for u32 {
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_varint().map(|i| i as u32)
}
}
@ -332,12 +354,24 @@ impl McBufReadable for i16 {
}
// u16 varint
impl McBufVarintReadable for u16 {
fn varint_read_into(buf: &mut impl Read) -> Result<Self, String> {
impl McBufVarReadable for u16 {
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_varint().map(|i| i as u16)
}
}
// Vec<T> varint
impl<T: McBufVarReadable> McBufVarReadable for Vec<T> {
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
let length = buf.read_varint()? as usize;
let mut contents = Vec::with_capacity(length);
for _ in 0..length {
contents.push(T::var_read_into(buf)?);
}
Ok(contents)
}
}
// i64
impl McBufReadable for i64 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {

71
azalea-protocol/src/mc_buf/write.rs Executable file → Normal file
View file

@ -5,7 +5,7 @@ use azalea_core::{
serializable_uuid::SerializableUuid, BlockPos, Direction, Slot,
};
use byteorder::{BigEndian, WriteBytesExt};
use std::io::Write;
use std::{collections::HashMap, io::Write};
use uuid::Uuid;
pub trait Writable: Write {
@ -146,8 +146,8 @@ pub trait McBufWritable {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error>;
}
pub trait McBufVarintWritable {
fn varint_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error>;
pub trait McBufVarWritable {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error>;
}
impl McBufWritable for i32 {
@ -156,8 +156,8 @@ impl McBufWritable for i32 {
}
}
impl McBufVarintWritable for i32 {
fn varint_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
impl McBufVarWritable for i32 {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_varint(*self)
}
}
@ -168,14 +168,24 @@ impl McBufWritable for UnsizedByteArray {
}
}
// TODO: use specialization when that gets stabilized into rust
// to optimize for Vec<u8> byte arrays
impl<T: McBufWritable> McBufWritable for Vec<T> {
default fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_list(self, |buf, i| T::write_into(i, buf))
}
}
impl<K: McBufWritable, V: McBufWritable> McBufWritable for HashMap<K, V> {
default fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
u32::var_write_into(&(self.len() as u32), buf)?;
for (key, value) in self {
key.write_into(buf)?;
value.write_into(buf)?;
}
Ok(())
}
}
impl McBufWritable for Vec<u8> {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_byte_array(self)
@ -204,16 +214,32 @@ impl McBufWritable for u32 {
}
// u32 varint
impl McBufVarintWritable for u32 {
fn varint_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i32::varint_write_into(&(*self as i32), buf)
impl McBufVarWritable for u32 {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i32::var_write_into(&(*self as i32), buf)
}
}
// Vec<T> varint
impl<T: McBufVarintWritable> McBufVarintWritable for Vec<T> {
fn varint_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_list(self, |buf, i| i.varint_write_into(buf))
impl McBufVarWritable for i64 {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut buffer = [0];
let mut cnt = 0;
let mut value = *self;
while value != 0 {
buffer[0] = (value & 0b0111_1111) as u8;
value = (value >> 7) & (i64::max_value() >> 6);
if value != 0 {
buffer[0] |= 0b1000_0000;
}
cnt += buf.write(&mut buffer)?;
}
Ok(())
}
}
impl McBufVarWritable for u64 {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i64::var_write_into(&(*self as i64), buf)
}
}
@ -225,9 +251,20 @@ impl McBufWritable for u16 {
}
// u16 varint
impl McBufVarintWritable for u16 {
fn varint_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i32::varint_write_into(&(*self as i32), buf)
impl McBufVarWritable for u16 {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i32::var_write_into(&(*self as i32), buf)
}
}
// Vec<T> varint
impl<T: McBufVarWritable> McBufVarWritable for Vec<T> {
fn var_write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
u32::var_write_into(&(self.len() as u32), buf)?;
for i in self {
i.var_write_into(buf)?;
}
Ok(())
}
}

View file

@ -3,11 +3,11 @@ use uuid::Uuid;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundAddEntityPacket {
#[varint]
#[var]
pub id: i32,
pub uuid: Uuid,
// TODO: have an entity type struct
#[varint]
#[var]
pub entity_type: i32,
pub x: f64,
pub y: f64,

View file

@ -3,11 +3,11 @@ use uuid::Uuid;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundAddMobPacket {
#[varint]
#[var]
pub id: i32,
pub uuid: Uuid,
// TODO: have an entity type struct
#[varint]
#[var]
pub entity_type: i32,
pub x: f64,
pub y: f64,

View file

@ -0,0 +1,14 @@
use packet_macros::GamePacket;
use uuid::Uuid;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundAddPlayerPacket {
#[var]
pub id: i32,
pub uuid: Uuid,
pub x: f64,
pub y: f64,
pub z: f64,
pub x_rot: i8,
pub y_rot: i8,
}

View file

@ -0,0 +1,20 @@
use packet_macros::{GamePacket, McBufReadable, McBufWritable};
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundAnimatePacket {
#[var]
pub id: u32,
pub action: AnimationAction,
}
// minecraft actually uses a u8 for this, but a varint still works and makes it
// so i don't have to add a special handler
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
pub enum AnimationAction {
SwingMainHand = 0,
Hurt = 1,
WakeUp = 2,
SwingOffHand = 3,
CriticalHit = 4,
MagicCriticalHit = 5,
}

View file

@ -0,0 +1,12 @@
use azalea_core::BlockPos;
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundBlockUpdatePacket {
pub pos: BlockPos,
// TODO: in vanilla this is a BlockState, but here we just have it as a number.
// however, we can't add azalea-world as a dependency because it depends on us.
// we could have a crate that contains encoding/decoding and the definitions?
#[var]
pub block_state: u32,
}

View file

@ -0,0 +1,17 @@
use azalea_chat::component::Component;
use packet_macros::{GamePacket, McBufReadable, McBufWritable};
use uuid::Uuid;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundChatPacket {
pub message: Component,
pub type_: ChatType,
pub sender: Uuid,
}
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
pub enum ChatType {
Chat = 0,
System = 1,
GameInfo = 2,
}

View file

@ -0,0 +1,11 @@
use azalea_core::Slot;
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundContainerSetContentPacket {
pub container_id: u8,
#[var]
pub state_id: u32,
pub items: Vec<Slot>,
pub carried_item: Slot,
}

View file

@ -2,7 +2,7 @@ use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundEntityVelocityPacket {
#[varint]
#[var]
pub entity_id: u32,
pub x_vel: i16,
pub y_vel: i16,

View file

@ -0,0 +1,17 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundInitializeBorderPacket {
pub new_center_x: f64,
pub new_center_z: f64,
pub old_size: f64,
pub new_size: f64,
#[var]
pub lerp_time: u64,
#[var]
pub new_absolute_max_size: u32,
#[var]
pub warning_blocks: u32,
#[var]
pub warning_time: u32,
}

View file

@ -0,0 +1,6 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundKeepAlivePacket {
pub id: u64,
}

View file

@ -22,7 +22,7 @@ pub struct ClientboundLevelChunkPacketData {
pub struct BlockEntity {
pub packed_xz: u8,
pub y: u16,
#[varint]
#[var]
pub type_: i32,
pub data: azalea_nbt::Tag,
}

View file

@ -0,0 +1,10 @@
use azalea_core::BlockPos;
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundLevelEventPacket {
pub type_: i32,
pub pos: BlockPos,
pub data: i32,
pub global_event: bool,
}

View file

@ -12,11 +12,11 @@ pub struct ClientboundLoginPacket {
pub dimension_type: azalea_nbt::Tag,
pub dimension: ResourceLocation,
pub seed: i64,
#[varint]
#[var]
pub max_players: i32,
#[varint]
#[var]
pub chunk_radius: i32,
#[varint]
#[var]
pub simulation_distance: i32,
pub reduced_debug_info: bool,
pub show_death_screen: bool,

View file

@ -0,0 +1,11 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundMoveEntityPosPacket {
#[var]
pub entity_id: i32,
pub xa: i16,
pub ya: i16,
pub za: i16,
pub on_ground: bool,
}

View file

@ -0,0 +1,13 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundMoveEntityPosRotPacket {
#[var]
pub entity_id: i32,
pub xa: i16,
pub ya: i16,
pub za: i16,
pub y_rot: i8,
pub x_rot: i8,
pub on_ground: bool,
}

View file

@ -0,0 +1,10 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundMoveEntityRotPacket {
#[var]
pub entity_id: i32,
pub y_rot: i8,
pub x_rot: i8,
pub on_ground: bool,
}

View file

@ -30,9 +30,9 @@ pub struct AddPlayer {
uuid: Uuid,
name: String,
properties: Vec<PlayerProperty>,
#[varint]
#[var]
gamemode: u32,
#[varint]
#[var]
ping: i32,
display_name: Option<Component>,
}
@ -40,14 +40,14 @@ pub struct AddPlayer {
#[derive(Clone, Debug, McBufReadable, McBufWritable)]
pub struct UpdateGameMode {
uuid: Uuid,
#[varint]
#[var]
gamemode: u32,
}
#[derive(Clone, Debug, McBufReadable, McBufWritable)]
pub struct UpdateLatency {
uuid: Uuid,
#[varint]
#[var]
ping: i32,
}

View file

@ -12,7 +12,7 @@ pub struct ClientboundPlayerPositionPacket {
pub relative_arguments: RelativeArguments,
/// Client should confirm this packet with Teleport Confirm containing the
/// same Teleport ID.
#[varint]
#[var]
pub id: i32,
pub dismount_vehicle: bool,
}

View file

@ -0,0 +1,7 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundRemoveEntitiesPacket {
#[var]
pub entity_ids: Vec<u32>,
}

View file

@ -0,0 +1,8 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundRotateHeadPacket {
#[var]
pub entity_id: u32,
pub y_head_rot: i8,
}

View file

@ -2,8 +2,8 @@ use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSetChunkCacheCenterPacket {
#[varint]
#[var]
pub x: i32,
#[varint]
#[var]
pub z: i32,
}

View file

@ -0,0 +1,8 @@
use azalea_core::BlockPos;
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSetDefaultSpawnPositionPacket {
pub pos: BlockPos,
pub angle: f32,
}

View file

@ -1,404 +1,9 @@
use crate::{
mc_buf::{Readable, Writable},
packets::{McBufReadable, McBufWritable},
};
use azalea_chat::component::Component;
use azalea_core::{BlockPos, Direction, Slot};
use packet_macros::{GamePacket, McBufReadable, McBufWritable};
use std::io::{Read, Write};
use uuid::Uuid;
use crate::mc_buf::EntityMetadata;
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSetEntityDataPacket {
#[varint]
#[var]
pub id: i32,
pub metadata: Vec<EntityDataItem>,
}
#[derive(Clone, Debug)]
pub struct EntityDataItem {
// we can't identify what the index is for here because we don't know the
// entity type
pub index: u8,
pub value: EntityDataValue,
}
impl McBufReadable for Vec<EntityDataItem> {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let mut metadata = Vec::new();
loop {
let index = buf.read_byte()?;
if index == 0xff {
break;
}
let value = EntityDataValue::read_into(buf)?;
metadata.push(EntityDataItem { index, value });
}
Ok(metadata)
}
}
impl McBufWritable for Vec<EntityDataItem> {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
for item in self {
buf.write_byte(item.index)?;
item.value.write_into(buf)?;
}
buf.write_byte(0xff)?;
Ok(())
}
}
#[derive(Clone, Debug)]
pub enum EntityDataValue {
Byte(u8),
// varint
Int(i32),
Float(f32),
String(String),
Component(Component),
OptionalComponent(Option<Component>),
ItemStack(Slot),
Boolean(bool),
Rotations { x: f32, y: f32, z: f32 },
BlockPos(BlockPos),
OptionalBlockPos(Option<BlockPos>),
Direction(Direction),
OptionalUuid(Option<Uuid>),
// 0 for absent (implies air); otherwise, a block state ID as per the global palette
// this is a varint
OptionalBlockState(Option<i32>),
CompoundTag(azalea_nbt::Tag),
Particle(Particle),
VillagerData(VillagerData),
// 0 for absent; 1 + actual value otherwise. Used for entity IDs.
OptionalUnsignedInt(Option<u32>),
Pose(Pose),
}
impl McBufReadable for EntityDataValue {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let type_ = buf.read_varint()?;
Ok(match type_ {
0 => EntityDataValue::Byte(buf.read_byte()?),
1 => EntityDataValue::Int(buf.read_varint()?),
2 => EntityDataValue::Float(buf.read_float()?),
3 => EntityDataValue::String(buf.read_utf()?),
4 => EntityDataValue::Component(Component::read_into(buf)?),
5 => EntityDataValue::OptionalComponent(Option::<Component>::read_into(buf)?),
6 => EntityDataValue::ItemStack(Slot::read_into(buf)?),
7 => EntityDataValue::Boolean(buf.read_boolean()?),
8 => EntityDataValue::Rotations {
x: buf.read_float()?,
y: buf.read_float()?,
z: buf.read_float()?,
},
9 => EntityDataValue::BlockPos(BlockPos::read_into(buf)?),
10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_into(buf)?),
11 => EntityDataValue::Direction(Direction::read_into(buf)?),
12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_into(buf)?),
13 => EntityDataValue::OptionalBlockState({
let val = i32::read_into(buf)?;
if val == 0 {
None
} else {
Some(val)
}
}),
14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_into(buf)?),
15 => EntityDataValue::Particle(Particle::read_into(buf)?),
16 => EntityDataValue::VillagerData(VillagerData::read_into(buf)?),
17 => EntityDataValue::OptionalUnsignedInt({
let val = buf.read_varint()?;
if val == 0 {
None
} else {
Some((val - 1) as u32)
}
}),
18 => EntityDataValue::Pose(Pose::read_into(buf)?),
_ => return Err(format!("Unknown entity data type: {}", type_)),
})
}
}
impl McBufWritable for EntityDataValue {
fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
todo!();
}
}
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
pub enum Pose {
Standing = 0,
FallFlying = 1,
Sleeping = 2,
Swimming = 3,
SpinAttack = 4,
Sneaking = 5,
LongJumping = 6,
Dying = 7,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct VillagerData {
#[varint]
type_: u32,
#[varint]
profession: u32,
#[varint]
level: u32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct Particle {
#[varint]
pub id: i32,
pub data: ParticleData,
}
#[derive(Clone, Debug)]
pub enum ParticleData {
AmbientEntityEffect,
AngryVillager,
Block(BlockParticle),
BlockMarker(BlockParticle),
Bubble,
Cloud,
Crit,
DamageIndicator,
DragonBreath,
DrippingLava,
FallingLava,
LandingLava,
DrippingWater,
FallingWater,
Dust(DustParticle),
DustColorTransition(DustColorTransitionParticle),
Effect,
ElderGuardian,
EnchantedHit,
Enchant,
EndRod,
EntityEffect,
ExplosionEmitter,
Explosion,
FallingDust(BlockParticle),
Firework,
Fishing,
Flame,
SoulFireFlame,
Soul,
Flash,
HappyVillager,
Composter,
Heart,
InstantEffect,
Item(ItemParticle),
Vibration(VibrationParticle),
ItemSlime,
ItemSnowball,
LargeSmoke,
Lava,
Mycelium,
Note,
Poof,
Portal,
Rain,
Smoke,
Sneeze,
Spit,
SquidInk,
SweepAttack,
TotemOfUndying,
Underwater,
Splash,
Witch,
BubblePop,
CurrentDown,
BubbleColumnUp,
Nautilus,
Dolphin,
CampfireCozySmoke,
CampfireSignalSmoke,
DrippingHoney,
FallingHoney,
LandingHoney,
FallingNectar,
FallingSporeBlossom,
Ash,
CrimsonSpore,
WarpedSpore,
SporeBlossomAir,
DrippingObsidianTear,
FallingObsidianTear,
LandingObsidianTear,
ReversePortal,
WhiteAsh,
SmallFlame,
Snowflake,
DrippingDripstoneLava,
FallingDripstoneLava,
DrippingDripstoneWater,
FallingDripstoneWater,
GlowSquidInk,
Glow,
WaxOn,
WaxOff,
ElectricSpark,
Scrape,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct BlockParticle {
#[varint]
pub block_state: i32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct DustParticle {
/// Red value, 0-1
pub red: f32,
/// Green value, 0-1
pub green: f32,
/// Blue value, 0-1
pub blue: f32,
/// The scale, will be clamped between 0.01 and 4.
pub scale: f32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct DustColorTransitionParticle {
/// Red value, 0-1
pub from_red: f32,
/// Green value, 0-1
pub from_green: f32,
/// Blue value, 0-1
pub from_blue: f32,
/// The scale, will be clamped between 0.01 and 4.
pub scale: f32,
/// Red value, 0-1
pub to_red: f32,
/// Green value, 0-1
pub to_green: f32,
/// Blue value, 0-1
pub to_blue: f32,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct ItemParticle {
pub item: Slot,
}
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
pub struct VibrationParticle {
pub origin: BlockPos,
pub position_type: String,
pub block_position: BlockPos,
#[varint]
pub entity_id: u32,
#[varint]
pub ticks: u32,
}
impl McBufReadable for ParticleData {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let id = buf.read_varint()?;
Ok(match id {
0 => ParticleData::AmbientEntityEffect,
1 => ParticleData::AngryVillager,
2 => ParticleData::Block(BlockParticle::read_into(buf)?),
3 => ParticleData::BlockMarker(BlockParticle::read_into(buf)?),
4 => ParticleData::Bubble,
5 => ParticleData::Cloud,
6 => ParticleData::Crit,
7 => ParticleData::DamageIndicator,
8 => ParticleData::DragonBreath,
9 => ParticleData::DrippingLava,
10 => ParticleData::FallingLava,
11 => ParticleData::LandingLava,
12 => ParticleData::DrippingWater,
13 => ParticleData::FallingWater,
14 => ParticleData::Dust(DustParticle::read_into(buf)?),
15 => ParticleData::DustColorTransition(DustColorTransitionParticle::read_into(buf)?),
16 => ParticleData::Effect,
17 => ParticleData::ElderGuardian,
18 => ParticleData::EnchantedHit,
19 => ParticleData::Enchant,
20 => ParticleData::EndRod,
21 => ParticleData::EntityEffect,
22 => ParticleData::ExplosionEmitter,
23 => ParticleData::Explosion,
24 => ParticleData::FallingDust(BlockParticle::read_into(buf)?),
25 => ParticleData::Firework,
26 => ParticleData::Fishing,
27 => ParticleData::Flame,
28 => ParticleData::SoulFireFlame,
29 => ParticleData::Soul,
30 => ParticleData::Flash,
31 => ParticleData::HappyVillager,
32 => ParticleData::Composter,
33 => ParticleData::Heart,
34 => ParticleData::InstantEffect,
35 => ParticleData::Item(ItemParticle::read_into(buf)?),
36 => ParticleData::Vibration(VibrationParticle::read_into(buf)?),
37 => ParticleData::ItemSlime,
38 => ParticleData::ItemSnowball,
39 => ParticleData::LargeSmoke,
40 => ParticleData::Lava,
41 => ParticleData::Mycelium,
42 => ParticleData::Note,
43 => ParticleData::Poof,
44 => ParticleData::Portal,
45 => ParticleData::Rain,
46 => ParticleData::Smoke,
47 => ParticleData::Sneeze,
48 => ParticleData::Spit,
49 => ParticleData::SquidInk,
50 => ParticleData::SweepAttack,
51 => ParticleData::TotemOfUndying,
52 => ParticleData::Underwater,
53 => ParticleData::Splash,
54 => ParticleData::Witch,
55 => ParticleData::BubblePop,
56 => ParticleData::CurrentDown,
57 => ParticleData::BubbleColumnUp,
58 => ParticleData::Nautilus,
59 => ParticleData::Dolphin,
60 => ParticleData::CampfireCozySmoke,
61 => ParticleData::CampfireSignalSmoke,
62 => ParticleData::DrippingHoney,
63 => ParticleData::FallingHoney,
64 => ParticleData::LandingHoney,
65 => ParticleData::FallingNectar,
66 => ParticleData::FallingSporeBlossom,
67 => ParticleData::Ash,
68 => ParticleData::CrimsonSpore,
69 => ParticleData::WarpedSpore,
70 => ParticleData::SporeBlossomAir,
71 => ParticleData::DrippingObsidianTear,
72 => ParticleData::FallingObsidianTear,
73 => ParticleData::LandingObsidianTear,
74 => ParticleData::ReversePortal,
75 => ParticleData::WhiteAsh,
76 => ParticleData::SmallFlame,
77 => ParticleData::Snowflake,
78 => ParticleData::DrippingDripstoneLava,
79 => ParticleData::FallingDripstoneLava,
80 => ParticleData::DrippingDripstoneWater,
81 => ParticleData::FallingDripstoneWater,
82 => ParticleData::GlowSquidInk,
83 => ParticleData::Glow,
84 => ParticleData::WaxOn,
85 => ParticleData::WaxOff,
86 => ParticleData::ElectricSpark,
87 => ParticleData::Scrape,
_ => return Err(format!("Unknown particle id: {}", id)),
})
}
}
impl McBufWritable for ParticleData {
fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
todo!()
}
pub metadata: EntityMetadata,
}

View file

@ -0,0 +1,10 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSetExperiencePacket {
pub experience_progress: f32,
#[var]
pub experience_level: u32,
#[var]
pub total_experience: u32,
}

View file

@ -0,0 +1,9 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSetHealthPacket {
pub health: f32,
#[var]
pub food: u32,
pub saturation: f32,
}

View file

@ -0,0 +1,7 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSetTimePacket {
pub game_time: u64,
pub day_time: u64,
}

View file

@ -0,0 +1,28 @@
use packet_macros::{GamePacket, McBufReadable, McBufWritable};
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundSoundPacket {
#[var]
/// TODO: use the sound registry instead of just being a u32
pub sound: u32,
pub source: SoundSource,
pub x: i32,
pub y: i32,
pub z: i32,
pub volume: f32,
pub pitch: f32,
}
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
pub enum SoundSource {
Master = 0,
Music = 1,
Records = 2,
Weather = 3,
Blocks = 4,
Hostile = 5,
Neutral = 6,
Players = 7,
Ambient = 8,
Voice = 9,
}

View file

@ -0,0 +1,13 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundTeleportEntityPacket {
#[var]
pub id: u32,
pub x: f64,
pub y: f64,
pub z: f64,
pub y_rot: i8,
pub x_rot: i8,
pub on_ground: bool,
}

View file

@ -0,0 +1,90 @@
use crate::packets::{McBufReadable, McBufWritable};
use azalea_chat::component::Component;
use azalea_core::{resource_location::ResourceLocation, Slot};
use packet_macros::{GamePacket, McBufReadable, McBufWritable};
use std::{
collections::HashMap,
io::{Read, Write},
};
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundUpdateAdvancementsPacket {
pub reset: bool,
pub added: HashMap<ResourceLocation, Advancement>,
pub removed: Vec<ResourceLocation>,
pub progress: HashMap<ResourceLocation, AdvancementProgress>,
}
#[derive(Clone, Debug, McBufReadable, McBufWritable)]
pub struct Advancement {
parent_id: Option<ResourceLocation>,
display: Option<DisplayInfo>,
// rewards: AdvancementRewards.EMPTY,
criteria: HashMap<ResourceLocation, Criterion>,
requirements: Vec<Vec<String>>,
// requirements_strategy: RequirementsStrategy.AND
}
#[derive(Clone, Debug, McBufReadable, McBufWritable)]
pub struct DisplayInfo {
pub title: Component,
pub description: Component,
pub icon: Slot,
pub frame: FrameType,
pub flags: DisplayFlags,
pub background: Option<ResourceLocation>,
pub x: f32,
pub y: f32,
}
#[derive(Clone, Debug)]
pub struct DisplayFlags {
pub background: bool,
pub show_toast: bool,
pub hidden: bool,
}
impl McBufReadable for DisplayFlags {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let data = u32::read_into(buf)?;
Ok(DisplayFlags {
background: (data & 0b1) != 0,
show_toast: (data & 0b10) != 0,
hidden: (data & 0b100) != 0,
})
}
}
impl McBufWritable for DisplayFlags {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let mut data = 0;
if self.background {
data |= 0b1;
}
if self.show_toast {
data |= 0b10;
}
if self.hidden {
data |= 0b100;
}
u32::write_into(&data, buf)
}
}
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
pub enum FrameType {
Task = 0,
Challenge = 1,
Goal = 2,
}
// nothing is written here
#[derive(Clone, Debug, McBufReadable, McBufWritable)]
pub struct Criterion {}
pub type AdvancementProgress = HashMap<ResourceLocation, CriterionProgress>;
#[derive(Clone, Debug, McBufReadable, McBufWritable)]
pub struct CriterionProgress {
date: Option<u64>,
}

View file

@ -6,7 +6,7 @@ use uuid::Uuid;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundUpdateAttributesPacket {
#[varint]
#[var]
pub entity_id: u32,
pub attributes: Vec<AttributeSnapshot>,
}

View file

@ -74,7 +74,7 @@ pub struct CookingRecipe {
ingredient: Ingredient,
result: Slot,
experience: f32,
#[varint]
#[var]
cooking_time: u32,
}
#[derive(Clone, Debug, McBufReadable, McBufWritable)]

View file

@ -2,6 +2,6 @@ use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ClientboundUpdateViewDistancePacket {
#[varint]
#[var]
pub view_distance: i32,
}

View file

@ -1,27 +1,48 @@
pub mod clientbound_add_entity_packet;
pub mod clientbound_add_mob_packet;
pub mod clientbound_add_player_packet;
pub mod clientbound_animate_packet;
pub mod clientbound_block_update_packet;
pub mod clientbound_change_difficulty_packet;
pub mod clientbound_chat_packet;
pub mod clientbound_container_set_content_packet;
pub mod clientbound_custom_payload_packet;
pub mod clientbound_declare_commands_packet;
pub mod clientbound_disconnect_packet;
pub mod clientbound_entity_event_packet;
pub mod clientbound_entity_velocity_packet;
pub mod clientbound_initialize_border_packet;
pub mod clientbound_keep_alive_packet;
pub mod clientbound_level_chunk_with_light_packet;
pub mod clientbound_level_event_packet;
pub mod clientbound_light_update_packet;
pub mod clientbound_login_packet;
pub mod clientbound_move_entity_pos_packet;
pub mod clientbound_move_entity_posrot_packet;
pub mod clientbound_move_entity_rot_packet;
pub mod clientbound_player_abilities_packet;
pub mod clientbound_player_info_packet;
pub mod clientbound_player_position_packet;
pub mod clientbound_recipe_packet;
pub mod clientbound_remove_entities_packet;
pub mod clientbound_rotate_head_packet;
pub mod clientbound_set_carried_item_packet;
pub mod clientbound_set_chunk_cache_center;
pub mod clientbound_set_default_spawn_position_packet;
pub mod clientbound_set_entity_data_packet;
pub mod clientbound_set_entity_link_packet;
pub mod clientbound_set_experience_packet;
pub mod clientbound_set_health_packet;
pub mod clientbound_set_time_packet;
pub mod clientbound_sound_packet;
pub mod clientbound_teleport_entity_packet;
pub mod clientbound_update_advancements_packet;
pub mod clientbound_update_attributes_packet;
pub mod clientbound_update_recipes_packet;
pub mod clientbound_update_tags_packet;
pub mod clientbound_update_view_distance_packet;
pub mod serverbound_custom_payload_packet;
pub mod serverbound_keep_alive_packet;
use packet_macros::declare_state_packets;
@ -29,30 +50,51 @@ declare_state_packets!(
GamePacket,
Serverbound => {
0x0a: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
0x0f: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
},
Clientbound => {
0x00: clientbound_add_entity_packet::ClientboundAddEntityPacket,
0x02: clientbound_add_mob_packet::ClientboundAddMobPacket,
0x04: clientbound_add_player_packet::ClientboundAddPlayerPacket,
0x6: clientbound_animate_packet::ClientboundAnimatePacket,
0xc: clientbound_block_update_packet::ClientboundBlockUpdatePacket,
0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
0xf: clientbound_chat_packet::ClientboundChatPacket,
0x12: clientbound_declare_commands_packet::ClientboundDeclareCommandsPacket,
0x14: clientbound_container_set_content_packet::ClientboundContainerSetContentPacket,
0x1a: clientbound_disconnect_packet::ClientboundDisconnectPacket,
0x1b: clientbound_entity_event_packet::ClientboundEntityEventPacket,
0x18: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket,
0x20: clientbound_initialize_border_packet::ClientboundInitializeBorderPacket,
0x21: clientbound_keep_alive_packet::ClientboundKeepAlivePacket,
0x22: clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket,
0x23: clientbound_level_event_packet::ClientboundLevelEventPacket,
0x25: clientbound_light_update_packet::ClientboundLightUpdatePacket,
0x26: clientbound_login_packet::ClientboundLoginPacket,
0x29: clientbound_move_entity_pos_packet::ClientboundMoveEntityPosPacket,
0x2a: clientbound_move_entity_posrot_packet::ClientboundMoveEntityPosRotPacket,
0x2b: clientbound_move_entity_rot_packet::ClientboundMoveEntityRotPacket,
0x32: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket,
0x36: clientbound_player_info_packet::ClientboundPlayerInfoPacket,
0x38: clientbound_player_position_packet::ClientboundPlayerPositionPacket,
0x39: clientbound_recipe_packet::ClientboundRecipePacket,
0x3a: clientbound_remove_entities_packet::ClientboundRemoveEntitiesPacket,
0x3e: clientbound_rotate_head_packet::ClientboundRotateHeadPacket,
0x48: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket,
0x49: clientbound_set_chunk_cache_center::ClientboundSetChunkCacheCenterPacket,
0x4a: clientbound_update_view_distance_packet::ClientboundUpdateViewDistancePacket,
0x4b: clientbound_set_default_spawn_position_packet::ClientboundSetDefaultSpawnPositionPacket,
0x4d: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket,
0x45: clientbound_set_entity_link_packet::ClientboundSetEntityLinkPacket,
0x4f: clientbound_entity_velocity_packet::ClientboundEntityVelocityPacket,
0x51: clientbound_set_experience_packet::ClientboundSetExperiencePacket,
0x52: clientbound_set_health_packet::ClientboundSetHealthPacket,
0x59: clientbound_set_time_packet::ClientboundSetTimePacket,
0x5d: clientbound_sound_packet::ClientboundSoundPacket,
0x62: clientbound_teleport_entity_packet::ClientboundTeleportEntityPacket,
0x63: clientbound_update_advancements_packet::ClientboundUpdateAdvancementsPacket,
0x64: clientbound_update_attributes_packet::ClientboundUpdateAttributesPacket,
0x66: clientbound_update_recipes_packet::ClientboundUpdateRecipesPacket,
0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket
0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket,
}
);

View file

@ -0,0 +1,6 @@
use packet_macros::GamePacket;
#[derive(Clone, Debug, GamePacket)]
pub struct ServerboundKeepAlivePacket {
pub id: u64,
}

View file

@ -4,7 +4,7 @@ use std::hash::Hash;
#[derive(Hash, Clone, Debug, HandshakePacket)]
pub struct ClientIntentionPacket {
#[varint]
#[var]
pub protocol_version: u32,
pub hostname: String,
pub port: u16,

View file

@ -5,7 +5,7 @@ use std::hash::Hash;
#[derive(Hash, Clone, Debug, LoginPacket)]
pub struct ClientboundCustomQueryPacket {
#[varint]
#[var]
pub transaction_id: u32,
pub identifier: ResourceLocation,
pub data: UnsizedByteArray,

View file

@ -1,6 +1,7 @@
use std::io::{Read, Write};
use super::LoginPacket;
use crate::mc_buf::McBufReadable;
use crate::mc_buf::{Readable, Writable};
use azalea_auth::game_profile::GameProfile;
use azalea_core::serializable_uuid::SerializableUuid;
@ -26,13 +27,7 @@ impl ClientboundGameProfilePacket {
}
pub fn read(buf: &mut impl Read) -> Result<LoginPacket, String> {
// TODO: we have a thing to read from the uuid now
let uuid = Uuid::from_int_array([
buf.read_int()? as u32,
buf.read_int()? as u32,
buf.read_int()? as u32,
buf.read_int()? as u32,
]);
let uuid = Uuid::read_into(buf)?;
let name = buf.read_utf_with_len(16)?;
Ok(ClientboundGameProfilePacket {
game_profile: GameProfile::new(uuid, name),

View file

@ -6,7 +6,7 @@ async fn main() {
println!("Hello, world!");
// let address = "95.111.249.143:10000";
let address = "172.23.192.1:62522";
let address = "172.23.192.1:61385";
// let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
// .await
// .unwrap();
@ -29,6 +29,9 @@ async fn main() {
// .unwrap();
// println!("{:?}", c);
}
Event::Chat(p) => {
println!("{}", p.message.to_ansi(None));
}
}
}

8
code-generator/README.md Normal file
View file

@ -0,0 +1,8 @@
Tools for automatically generating code to help with updating Minecraft versions.
The directory name doesn't start with `azalea-` because it's not a Rust crate.
## Usage
Generate packet:\
`python main.py [packet id] [clientbound or serverbound] \[game/handshake/login/status\]`

62
code-generator/main.py Normal file
View file

@ -0,0 +1,62 @@
from mappings import Mappings
import packetcodegen
import requests
import json
import sys
import os
print(
f'\033[92mFinding Minecraft version...\033[m')
version_manifest_data = requests.get(
'https://launchermeta.mojang.com/mc/game/version_manifest.json').json()
minecraft_version = version_manifest_data['latest']['release']
print(
f'\033[92mUsing \033[1m{minecraft_version}..\033[m')
package_url = next(
filter(lambda v: v['id'] == minecraft_version, version_manifest_data['versions']))['url']
package_data = requests.get(package_url).json()
client_jar_url = package_data['downloads']['client']['url']
skipping_burger = False
try:
with open('burger.json', 'r') as f:
burger_data = json.load(f)[0]
if burger_data['version']['id'] == minecraft_version:
skipping_burger = True
print(
f'\033[92mSkipping Burger step because the burger.json is up-to-date.\033[m')
except FileNotFoundError:
pass
if not skipping_burger:
print('\033[92mDownloading Burger...\033[m')
r = os.system('git clone https://github.com/pokechu22/Burger')
os.system('cd Burger && git pull')
# print('\033[92mInstalling dependencies...\033[m')
# os.system('cd Burger && pip install six jawa')
print('\033[92mDownloading client jar...\033[m')
with open('client.jar', 'wb') as f:
f.write(requests.get(client_jar_url).content)
print(f'\033[92mExtracting data with Burger...\033[m')
os.system(
'cd Burger && python munch.py ../client.jar --output ../burger.json')
client_mappings_url = package_data['downloads']['client_mappings']['url']
mappings = Mappings.parse(requests.get(client_mappings_url).text)
with open('burger.json', 'r') as f:
burger_data = json.load(f)
burger_packets_data = burger_data[0]['packets']['packet']
packet_id, direction, state = int(sys.argv[1]), sys.argv[2], sys.argv[3]
print(
f'Generating code for packet id: {packet_id} with direction {direction} and state {state}')
packetcodegen.generate(burger_packets_data, mappings,
packet_id, direction, state)
os.system('cd .. && cargo fmt')
print('Done!')

View file

@ -0,0 +1,60 @@
class Mappings:
__slots__ = ('classes', 'fields', 'methods')
def __init__(self, classes, fields, methods):
self.classes = classes
self.fields = fields
self.methods = methods
@staticmethod
def parse(mappings_txt):
classes = {}
fields = {}
methods = {}
current_obfuscated_class_name = None
for line in mappings_txt.splitlines():
if line.startswith('#') or line == '':
continue
if line.startswith(' '):
# if a line starts with 4 spaces, that means it's a method or a field
if '(' in line:
# if it has an opening parenthesis, it's a method
real_name_with_parameters_and_line, obfuscated_name = line.strip().split(' -> ')
real_name_with_parameters = real_name_with_parameters_and_line.split(
':')[-1]
real_name = real_name_with_parameters.split('(')[0]
parameters = real_name_with_parameters.split('(')[1]
if current_obfuscated_class_name not in methods:
methods[current_obfuscated_class_name] = {}
methods[current_obfuscated_class_name][
f'{obfuscated_name}({parameters})'] = real_name
else:
# otherwise, it's a field
real_name_with_type, obfuscated_name = line.strip().split(' -> ')
real_name = real_name_with_type.split(' ')[1]
if current_obfuscated_class_name not in fields:
fields[current_obfuscated_class_name] = {}
fields[current_obfuscated_class_name][obfuscated_name] = real_name
else:
# otherwise it's a class
real_name, obfuscated_name = line.strip(':').split(' -> ')
current_obfuscated_class_name = obfuscated_name
classes[obfuscated_name] = real_name
return Mappings(classes, fields, methods)
def get_field(self, obfuscated_class_name, obfuscated_field_name):
return self.fields.get(obfuscated_class_name, {}).get(obfuscated_field_name)
def get_class(self, obfuscated_class_name):
return self.classes[obfuscated_class_name]
def get_method(self, obfuscated_class_name, obfuscated_method_name, obfuscated_signature):
return self.methods[obfuscated_class_name][f'{obfuscated_method_name}({obfuscated_signature})']

View file

@ -0,0 +1,171 @@
from utils import to_snake_case, to_camel_case
from mappings import Mappings
import os
def burger_type_to_rust_type(burger_type):
is_var = False
uses = set()
if burger_type == 'byte':
field_type_rs = 'i8'
elif burger_type == 'short':
field_type_rs = 'i16'
elif burger_type == 'int':
field_type_rs = 'i32'
elif burger_type == 'long':
field_type_rs = 'i64'
elif burger_type == 'float':
field_type_rs = 'f32'
elif burger_type == 'double':
field_type_rs = 'f64'
elif burger_type == 'varint':
is_var = True
field_type_rs = 'i32'
elif burger_type == 'varlong':
is_var = True
field_type_rs = 'i64'
elif burger_type == 'boolean':
field_type_rs = 'bool'
elif burger_type == 'string':
field_type_rs = 'String'
elif burger_type == 'chatcomponent':
field_type_rs = 'Component'
uses.add('azalea_chat::component::Component')
elif burger_type == 'identifier':
field_type_rs = 'ResourceLocation'
uses.add('azalea_core::resource_location::ResourceLocation')
elif burger_type == 'uuid':
field_type_rs = 'Uuid'
uses.add('uuid::Uuid')
elif burger_type == 'position':
field_type_rs = 'BlockPos'
uses.add('azalea_core::BlockPos')
elif burger_type == 'nbtcompound':
field_type_rs = 'azalea_nbt::Tag'
elif burger_type == 'itemstack':
field_type_rs = 'Slot'
uses.add('azalea_core::Slot')
elif burger_type == 'metadata':
field_type_rs = 'EntityMetadata'
uses.add('crate::mc_buf::EntityMetadata')
elif burger_type == 'enum':
# enums are too complicated, leave those to the user
field_type_rs = 'todo!()'
elif burger_type.endswith('[]'):
field_type_rs, is_var, uses = burger_type_to_rust_type(
burger_type[:-2])
field_type_rs = f'Vec<{field_type_rs}>'
else:
print('Unknown field type:', burger_type)
exit()
return field_type_rs, is_var, uses
def write_packet_file(state, packet_name_snake_case, code):
with open(f'../azalea-protocol/src/packets/{state}/{packet_name_snake_case}.rs', 'w') as f:
f.write(code)
def generate(burger_packets, mappings: Mappings, target_packet_id, target_packet_direction, target_packet_state):
for packet in burger_packets.values():
if packet['id'] != target_packet_id:
continue
direction = packet['direction'].lower() # serverbound or clientbound
state = {'PLAY': 'game'}.get(packet['state'], packet['state'].lower())
if state != target_packet_state or direction != target_packet_direction:
continue
generated_packet_code = []
uses = set()
generated_packet_code.append(
f'#[derive(Clone, Debug, {to_camel_case(state)}Packet)]')
uses.add(f'packet_macros::{to_camel_case(state)}Packet')
obfuscated_class_name = packet['class'].split('.')[0].split('$')[0]
class_name = mappings.get_class(
obfuscated_class_name).split('.')[-1].split('$')[0]
generated_packet_code.append(
f'pub struct {to_camel_case(class_name)} {{')
for instruction in packet.get('instructions', []):
if instruction['operation'] == 'write':
obfuscated_field_name = instruction['field']
if '.' in obfuscated_field_name or ' ' in obfuscated_field_name or '(' in obfuscated_field_name:
generated_packet_code.append(f'// TODO: {instruction}')
continue
field_name = mappings.get_field(
obfuscated_class_name, obfuscated_field_name)
if not field_name:
generated_packet_code.append(
f'// TODO: unknown field {instruction}')
continue
field_type = instruction['type']
field_type_rs, is_var, instruction_uses = burger_type_to_rust_type(
field_type)
if is_var:
generated_packet_code.append('#[var]')
generated_packet_code.append(
f'pub {to_snake_case(field_name)}: {field_type_rs},')
uses.update(instruction_uses)
else:
generated_packet_code.append(f'// TODO: {instruction}')
continue
generated_packet_code.append('}')
if uses:
# empty line before the `use` statements
generated_packet_code.insert(0, '')
for use in uses:
generated_packet_code.insert(0, f'use {use};')
print(generated_packet_code)
write_packet_file(state, to_snake_case(class_name),
'\n'.join(generated_packet_code))
print()
mod_rs_dir = f'../azalea-protocol/src/packets/{state}/mod.rs'
with open(mod_rs_dir, 'r') as f:
mod_rs = f.read().splitlines()
pub_mod_line = f'pub mod {to_snake_case(class_name)};'
if pub_mod_line not in mod_rs:
mod_rs.insert(0, pub_mod_line)
packet_mod_rs_line = f' {hex(packet["id"])}: {to_snake_case(class_name)}::{to_camel_case(class_name)},'
in_serverbound = False
in_clientbound = False
for i, line in enumerate(mod_rs):
if line.strip() == 'Serverbound => {':
in_serverbound = True
continue
elif line.strip() == 'Clientbound => {':
in_clientbound = True
continue
elif line.strip() in ('}', '},'):
if (in_serverbound and direction == 'serverbound') or (in_clientbound and direction == 'clientbound'):
mod_rs.insert(i, packet_mod_rs_line)
break
in_serverbound = in_clientbound = False
continue
if line.strip() == '' or line.strip().startswith('//') or (not in_serverbound and direction == 'serverbound') or (not in_clientbound and direction == 'clientbound'):
continue
line_packet_id_hex = line.strip().split(':')[0]
assert line_packet_id_hex.startswith('0x')
line_packet_id = int(line_packet_id_hex[2:], 16)
if line_packet_id > packet['id']:
mod_rs.insert(i, packet_mod_rs_line)
break
with open(mod_rs_dir, 'w') as f:
f.write('\n'.join(mod_rs))

15
code-generator/utils.py Normal file
View file

@ -0,0 +1,15 @@
import urllib.request
import gzip
import json
import re
import io
def to_snake_case(name):
s = re.sub('([A-Z])', r'_\1', name)
return s.lower().strip('_')
def to_camel_case(name):
s = re.sub('_([a-z])', lambda m: m.group(1).upper(), name)
return s[0].upper() + s[1:]