1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

Merge pull request #7 from mat-1/1.19

1.19
This commit is contained in:
mat 2022-06-08 23:37:54 +00:00 committed by GitHub
commit 601637bd48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1112 additions and 651 deletions

7
.gitignore vendored
View file

@ -3,10 +3,3 @@
flamegraph.svg
perf.data
perf.data.old
code-generator/Burger
code-generator/client.jar
code-generator/burger.json
__pycache__
*.tmp

View file

@ -6,6 +6,9 @@ A Rust crate for creating Minecraft bots.
<img src="https://cdn.matdoes.dev/images/flowering_azalea.webp" alt="Azalea" height="200">
</p>
<!-- The line below is automatically read and updated by the migrate script, so don't change it manually. -->
*Currently supported Minecraft version: `1.19`.*
I named this Azalea because it sounds like a cool word and this is a cool library. This project was heavily inspired by PrismarineJS.
## Why

View file

@ -4,14 +4,16 @@ use azalea_protocol::{
connect::{GameConnection, HandshakeConnection},
packets::{
game::{
clientbound_chat_packet::ClientboundChatPacket,
clientbound_player_chat_packet::ClientboundPlayerChatPacket,
clientbound_system_chat_packet::ClientboundSystemChatPacket,
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
serverbound_keep_alive_packet::ServerboundKeepAlivePacket, GamePacket,
},
handshake::client_intention_packet::ClientIntentionPacket,
login::{
serverbound_hello_packet::ServerboundHelloPacket,
serverbound_key_packet::ServerboundKeyPacket, LoginPacket,
serverbound_key_packet::{NonceOrSaltSignature, ServerboundKeyPacket},
LoginPacket,
},
ConnectionProtocol, PROTOCOL_VERSION,
},
@ -43,10 +45,25 @@ pub struct Client {
// game_loop
}
#[derive(Debug, Clone)]
pub enum ChatPacket {
System(ClientboundSystemChatPacket),
Player(ClientboundPlayerChatPacket),
}
// impl ChatPacket {
// pub fn message(&self) -> &str {
// match self {
// ChatPacket::System(p) => &p.content,
// ChatPacket::Player(p) => &p.message,
// }
// }
// }
#[derive(Debug, Clone)]
pub enum Event {
Login,
Chat(ClientboundChatPacket),
Chat(ChatPacket),
}
/// Whether we should ignore errors when decoding packets.
@ -75,6 +92,7 @@ impl Client {
conn.write(
ServerboundHelloPacket {
username: account.username.clone(),
public_key: None,
}
.get(),
)
@ -92,8 +110,10 @@ impl Client {
conn.write(
ServerboundKeyPacket {
nonce: e.encrypted_nonce,
shared_secret: e.encrypted_public_key,
nonce_or_salt_signature: NonceOrSaltSignature::Nonce(
e.encrypted_nonce,
),
key_bytes: e.encrypted_public_key,
}
.get(),
)
@ -179,27 +199,66 @@ impl Client {
println!("Got login packet {:?}", p);
let mut state = state.lock().await;
// // write p into login.txt
// std::io::Write::write_all(
// &mut std::fs::File::create("login.txt").unwrap(),
// format!("{:#?}", p).as_bytes(),
// )
// .unwrap();
state.player.entity.id = p.player_id;
let dimension_type = p
.dimension_type
// TODO: have registry_holder be a struct because this sucks rn
// best way would be to add serde support to azalea-nbt
let registry_holder = p
.registry_holder
.as_compound()
.expect("Dimension type is not compound")
.expect("Registry holder is not a compound")
.get("")
.expect("No \"\" tag")
.as_compound()
.expect("\"\" tag is not compound");
.expect("\"\" tag is not a compound");
let dimension_types = registry_holder
.get("minecraft:dimension_type")
.expect("No dimension_type tag")
.as_compound()
.expect("dimension_type is not a compound")
.get("value")
.expect("No dimension_type value")
.as_list()
.expect("dimension_type value is not a list");
let dimension_type = dimension_types
.iter()
.find(|t| {
t.as_compound()
.expect("dimension_type value is not a compound")
.get("name")
.expect("No name tag")
.as_string()
.expect("name is not a string")
== p.dimension_type.to_string()
})
.expect(&format!("No dimension_type with name {}", p.dimension_type))
.as_compound()
.unwrap()
.get("element")
.expect("No element tag")
.as_compound()
.expect("element is not a compound");
let height = (*dimension_type
.get("height")
.expect("No height tag")
.as_int()
.expect("height tag is not int"))
.expect("height tag is not an int"))
.try_into()
.expect("height is not a u32");
let min_y = (*dimension_type
.get("min_y")
.expect("No min_y tag")
.as_int()
.expect("min_y tag is not int"))
.expect("min_y tag is not an int"))
.try_into()
.expect("min_y is not an i32");
@ -290,9 +349,6 @@ impl Client {
GamePacket::ClientboundLightUpdatePacket(p) => {
println!("Got light update packet {:?}", p);
}
GamePacket::ClientboundAddMobPacket(p) => {
println!("Got add mob packet {:?}", p);
}
GamePacket::ClientboundAddEntityPacket(p) => {
println!("Got add entity packet {:?}", p);
}
@ -357,9 +413,13 @@ impl Client {
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::ClientboundPlayerChatPacket(p) => {
println!("Got player chat packet {:?}", p);
tx.send(Event::Chat(ChatPacket::Player(p.clone()))).unwrap();
}
GamePacket::ClientboundSystemChatPacket(p) => {
println!("Got system chat packet {:?}", p);
tx.send(Event::Chat(ChatPacket::System(p.clone()))).unwrap();
}
GamePacket::ClientboundSoundPacket(p) => {
println!("Got sound packet {:?}", p);
@ -384,6 +444,9 @@ impl Client {
GamePacket::ClientboundLevelParticlesPacket(p) => {
println!("Got level particles packet {:?}", p);
}
GamePacket::ClientboundServerDataPacket(p) => {
println!("Got server data packet {:?}", p);
}
_ => panic!("Unexpected packet {:?}", packet),
}
}

View file

@ -11,7 +11,9 @@ mod slot;
pub use slot::{Slot, SlotData};
mod position;
pub use position::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos};
pub use position::{
BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos, GlobalPos,
};
mod direction;
pub use direction::Direction;

View file

@ -1,5 +1,7 @@
use std::ops::Rem;
use crate::resource_location::ResourceLocation;
#[derive(Clone, Copy, Debug, Default)]
pub struct BlockPos {
pub x: i32,
@ -137,6 +139,14 @@ impl From<&ChunkBlockPos> for ChunkSectionBlockPos {
}
}
/// A block pos with an attached dimension
#[derive(Debug, Clone)]
pub struct GlobalPos {
pub pos: BlockPos,
// this is actually a ResourceKey in Minecraft, but i don't think it matters?
pub dimension: ResourceLocation,
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -1,3 +1,5 @@
mod signing;
use aes::cipher::inout::InOutBuf;
use aes::{
cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit},
@ -5,6 +7,7 @@ use aes::{
};
use rand::{rngs::OsRng, RngCore};
use sha1::{Digest, Sha1};
pub use signing::SaltSignaturePair;
fn generate_secret_key() -> [u8; 16] {
let mut key = [0u8; 16];
@ -65,7 +68,6 @@ pub fn create_cipher(key: &[u8]) -> (Aes128CfbEnc, Aes128CfbDec) {
)
}
// wow this is terrible
pub fn encrypt_packet(cipher: &mut Aes128CfbEnc, packet: &mut [u8]) {
let (chunks, rest) = InOutBuf::from(packet).into_chunks();
assert!(rest.is_empty());

View file

@ -0,0 +1,5 @@
#[derive(Debug, Clone)]
pub struct SaltSignaturePair {
pub salt: u64,
pub signature: Vec<u8>,
}

View file

@ -1,3 +1,5 @@
# Azalea NBT
A fast NBT serializer and deserializer.
TODO: serde support for fast registry_holder parsing in azalea-client

View file

@ -2,7 +2,8 @@
pub enum Error {
InvalidTagType(u8),
InvalidTag,
WriteError,
WriteError(std::io::Error),
Utf8Error(std::string::FromUtf8Error),
}
impl std::fmt::Display for Error {
@ -10,18 +11,19 @@ impl std::fmt::Display for Error {
match self {
Error::InvalidTagType(id) => write!(f, "Invalid tag type: {}", id),
Error::InvalidTag => write!(f, "Invalid tag"),
Error::WriteError => write!(f, "Write error"),
Error::WriteError(e) => write!(f, "Write error: {}", e),
Error::Utf8Error(e) => write!(f, "Utf8 error: {}", e),
}
}
}
impl From<std::io::Error> for Error {
fn from(_: std::io::Error) -> Self {
Error::WriteError
fn from(e: std::io::Error) -> Self {
Error::WriteError(e)
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(_: std::string::FromUtf8Error) -> Self {
Error::WriteError
fn from(e: std::string::FromUtf8Error) -> Self {
Error::Utf8Error(e)
}
}

View file

@ -12,13 +12,16 @@ Unfortunately, using azalea-protocol requires Rust nightly because [specializati
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.
1. First, you'll need the packet id. You can get this from azalea-protocol error messages, or from wiki.vg.
2. Run `python codegen/newpacket.py [packet id] [clientbound or serverbound] \[game/handshake/login/status\]`\
3. Go to the directory where it told you the packet was generated. If there's no comments, you're done. Otherwise, keep going.
4. 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`.
5. Add the fields from Minecraft's source code from either the read or write methods. 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.
6. Format the code, submit a pull request, and wait for it to be reviewed.
### Implementing packets
You can manually implement reading and writing functionality for a packet by implementing McBufReadable and McBufWritable, but you can also have this automatically generated for a struct or enum by deriving McBuf.
Look at other packets as an example.

View file

@ -56,13 +56,23 @@ fn create_impl_mcbufreadable(ident: &Ident, data: &Data) -> proc_macro2::TokenSt
}
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let mut match_contents = quote!();
let mut variant_discrim: u32 = 0;
for variant in variants {
let variant_name = &variant.ident;
let variant_discrim = &variant
.discriminant
.as_ref()
.expect("enum variant must have a discriminant")
.1;
match &variant.discriminant.as_ref() {
Some(d) => {
variant_discrim = match &d.1 {
syn::Expr::Lit(e) => match &e.lit {
syn::Lit::Int(i) => i.base10_parse().unwrap(),
_ => panic!("Error parsing enum discriminant"),
},
_ => panic!("Error parsing enum discriminant"),
}
}
None => {
variant_discrim += 1;
}
}
match_contents.extend(quote! {
#variant_discrim => Ok(Self::#variant_name),
});
@ -344,6 +354,7 @@ pub fn declare_state_packets(input: TokenStream) -> TokenStream {
});
}
for PacketIdPair { id, module, name } in input.clientbound.packets {
// let name_litstr = syn::LitStr::new(&name.to_string(), name.span());
enum_contents.extend(quote! {
#name(#module::#name),
});

View file

@ -1,6 +1,7 @@
//! This lib is responsible for parsing Minecraft packets.
#![feature(min_specialization)]
#![feature(arbitrary_enum_discriminant)]
use std::net::IpAddr;
use std::str::FromStr;

View file

@ -2,8 +2,10 @@ use super::{UnsizedByteArray, MAX_STRING_LENGTH};
use azalea_chat::component::Component;
use azalea_core::{
difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation,
serializable_uuid::SerializableUuid, BlockPos, ChunkSectionPos, Direction, Slot, SlotData,
serializable_uuid::SerializableUuid, BlockPos, ChunkSectionPos, Direction, GlobalPos, Slot,
SlotData,
};
use azalea_crypto::SaltSignaturePair;
use byteorder::{ReadBytesExt, BE};
use serde::Deserialize;
use std::{collections::HashMap, hash::Hash, io::Read};
@ -311,56 +313,48 @@ impl McBufReadable for Vec<u8> {
}
}
// string
impl McBufReadable for String {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_utf()
}
}
// ResourceLocation
impl McBufReadable for ResourceLocation {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_resource_location()
}
}
// u32
impl McBufReadable for u32 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
Readable::read_int(buf).map(|i| i as u32)
}
}
// u32 varint
impl McBufVarReadable for u32 {
fn var_read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_varint().map(|i| i as u32)
}
}
// u16
impl McBufReadable for u16 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_short().map(|i| i as u16)
}
}
// i16
impl McBufReadable for i16 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_short()
}
}
// u16 varint
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;
@ -372,70 +366,60 @@ impl<T: McBufVarReadable> McBufVarReadable for Vec<T> {
}
}
// i64
impl McBufReadable for i64 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_long()
}
}
// u64
impl McBufReadable for u64 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
i64::read_into(buf).map(|i| i as u64)
}
}
// bool
impl McBufReadable for bool {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_boolean()
}
}
// u8
impl McBufReadable for u8 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_byte()
}
}
// i8
impl McBufReadable for i8 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_byte().map(|i| i as i8)
}
}
// f32
impl McBufReadable for f32 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_float()
}
}
// f64
impl McBufReadable for f64 {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_double()
}
}
// GameType
impl McBufReadable for GameType {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
GameType::from_id(buf.read_byte()?)
}
}
// Option<GameType>
impl McBufReadable for Option<GameType> {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
GameType::from_optional_id(buf.read_byte()? as i8)
}
}
// Option<String>
impl<T: McBufReadable> McBufReadable for Option<T> {
default fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let present = buf.read_boolean()?;
@ -447,21 +431,18 @@ impl<T: McBufReadable> McBufReadable for Option<T> {
}
}
// azalea_nbt::Tag
impl McBufReadable for azalea_nbt::Tag {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_nbt()
}
}
// Difficulty
impl McBufReadable for Difficulty {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
Ok(Difficulty::by_id(u8::read_into(buf)?))
}
}
// Component
impl McBufReadable for Component {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let string = buf.read_utf()?;
@ -472,7 +453,6 @@ impl McBufReadable for Component {
}
}
// Slot
impl McBufReadable for Slot {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let present = buf.read_boolean()?;
@ -486,14 +466,12 @@ impl McBufReadable for Slot {
}
}
// Uuid
impl McBufReadable for Uuid {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
buf.read_uuid()
}
}
// BlockPos
impl McBufReadable for BlockPos {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let val = u64::read_into(buf)?;
@ -504,7 +482,15 @@ impl McBufReadable for BlockPos {
}
}
// Direction
impl McBufReadable for GlobalPos {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
Ok(GlobalPos {
pos: BlockPos::read_into(buf)?,
dimension: ResourceLocation::read_into(buf)?,
})
}
}
impl McBufReadable for Direction {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
match buf.read_varint()? {
@ -519,7 +505,6 @@ impl McBufReadable for Direction {
}
}
// ChunkSectionPos
impl McBufReadable for ChunkSectionPos {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let long = i64::read_into(buf)?;
@ -530,3 +515,11 @@ impl McBufReadable for ChunkSectionPos {
})
}
}
impl McBufReadable for SaltSignaturePair {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let salt = u64::read_into(buf)?;
let signature = Vec::<u8>::read_into(buf)?;
Ok(SaltSignaturePair { salt, signature })
}
}

View file

@ -2,8 +2,9 @@ use super::{UnsizedByteArray, MAX_STRING_LENGTH};
use azalea_chat::component::Component;
use azalea_core::{
difficulty::Difficulty, game_type::GameType, resource_location::ResourceLocation,
serializable_uuid::SerializableUuid, BlockPos, ChunkSectionPos, Direction, Slot,
serializable_uuid::SerializableUuid, BlockPos, ChunkSectionPos, Direction, GlobalPos, Slot,
};
use azalea_crypto::SaltSignaturePair;
use byteorder::{BigEndian, WriteBytesExt};
use std::{collections::HashMap, io::Write};
use uuid::Uuid;
@ -192,28 +193,24 @@ impl McBufWritable for Vec<u8> {
}
}
// string
impl McBufWritable for String {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_utf(self)
}
}
// ResourceLocation
impl McBufWritable for ResourceLocation {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_resource_location(self)
}
}
// u32
impl McBufWritable for u32 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i16::write_into(&(*self as i16), buf)
}
}
// u32 varint
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)
@ -243,21 +240,18 @@ impl McBufVarWritable for u64 {
}
}
// u16
impl McBufWritable for u16 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i16::write_into(&(*self as i16), buf)
}
}
// u16 varint
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)?;
@ -268,77 +262,66 @@ impl<T: McBufVarWritable> McBufVarWritable for Vec<T> {
}
}
// u8
impl McBufWritable for u8 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_byte(*self)
}
}
// i16
impl McBufWritable for i16 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
Writable::write_short(buf, *self)
}
}
// i64
impl McBufWritable for i64 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
Writable::write_long(buf, *self)
}
}
// u64
impl McBufWritable for u64 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
i64::write_into(&(*self as i64), buf)
}
}
// bool
impl McBufWritable for bool {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_boolean(*self)
}
}
// i8
impl McBufWritable for i8 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_byte(*self as u8)
}
}
// f32
impl McBufWritable for f32 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_float(*self)
}
}
// f64
impl McBufWritable for f64 {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_double(*self)
}
}
// GameType
impl McBufWritable for GameType {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
u8::write_into(&self.to_id(), buf)
}
}
// Option<GameType>
impl McBufWritable for Option<GameType> {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_byte(GameType::to_optional_id(self) as u8)
}
}
// Option<String>
impl<T: McBufWritable> McBufWritable for Option<T> {
default fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
if let Some(s) = self {
@ -351,21 +334,18 @@ impl<T: McBufWritable> McBufWritable for Option<T> {
}
}
// azalea_nbt::Tag
impl McBufWritable for azalea_nbt::Tag {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_nbt(self)
}
}
// Difficulty
impl McBufWritable for Difficulty {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
u8::write_into(&self.id(), buf)
}
}
// Component
impl McBufWritable for Component {
// async fn read_into(buf: &mut impl Read) -> Result<Self, String>
// where
@ -383,7 +363,6 @@ impl McBufWritable for Component {
}
}
// Slot
impl McBufWritable for Slot {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
match self {
@ -399,7 +378,6 @@ impl McBufWritable for Slot {
}
}
// Slot
impl McBufWritable for Uuid {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_uuid(self)?;
@ -408,7 +386,6 @@ impl McBufWritable for Uuid {
}
}
// BlockPos
impl McBufWritable for BlockPos {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_long(
@ -419,14 +396,21 @@ impl McBufWritable for BlockPos {
}
}
// Direction
impl McBufWritable for GlobalPos {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
BlockPos::write_into(&self.pos, buf)?;
ResourceLocation::write_into(&self.dimension, buf)?;
Ok(())
}
}
impl McBufWritable for Direction {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_varint(*self as i32)
}
}
// ChunkSectionPos
impl McBufWritable for ChunkSectionPos {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
let long = (((self.x & 0x3FFFFF) as i64) << 42)
@ -436,3 +420,11 @@ impl McBufWritable for ChunkSectionPos {
Ok(())
}
}
impl McBufWritable for SaltSignaturePair {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
self.salt.write_into(buf)?;
self.signature.write_into(buf)?;
Ok(())
}
}

View file

@ -4,9 +4,9 @@ use uuid::Uuid;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundAddEntityPacket {
#[var]
pub id: i32,
pub id: u32,
pub uuid: Uuid,
// TODO: have an entity type struct
// TODO: have an entity type enum/struct
#[var]
pub entity_type: i32,
pub x: f64,
@ -14,9 +14,10 @@ pub struct ClientboundAddEntityPacket {
pub z: f64,
pub x_rot: i8,
pub y_rot: i8,
// pub y_head_rot: i8,
pub y_head_rot: i8,
#[var]
pub data: i32,
pub x_vel: u16,
pub y_vel: u16,
pub z_vel: u16,
pub x_vel: i16,
pub y_vel: i16,
pub z_vel: i16,
}

View file

@ -1,21 +0,0 @@
use packet_macros::{GamePacket, McBuf};
use uuid::Uuid;
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundAddMobPacket {
#[var]
pub id: i32,
pub uuid: Uuid,
// TODO: have an entity type struct
#[var]
pub entity_type: i32,
pub x: f64,
pub y: f64,
pub z: f64,
pub x_rot: i8,
pub y_rot: i8,
pub y_head_rot: i8,
pub x_vel: u16,
pub y_vel: u16,
pub z_vel: u16,
}

View file

@ -0,0 +1,7 @@
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundBlockChangedAckPacket {
#[var]
pub sequence: i32,
}

View file

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

View file

@ -0,0 +1,8 @@
use azalea_chat::component::Component;
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundChatPreviewPacket {
pub query_id: i32,
pub preview: Option<Component>,
}

View file

@ -1,43 +1,20 @@
use super::GamePacket;
use crate::mc_buf::McBufVarReadable;
use crate::mc_buf::{McBufReadable, McBufWritable, Readable, Writable};
use azalea_core::resource_location::ResourceLocation;
use packet_macros::{GamePacket, McBuf};
use std::{
hash::Hash,
io::{Read, Write},
};
#[derive(Hash, Clone, Debug)]
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundDeclareCommandsPacket {
pub entries: Vec<BrigadierNodeStub>,
#[var]
pub root_index: i32,
}
impl ClientboundDeclareCommandsPacket {
pub fn get(self) -> GamePacket {
GamePacket::ClientboundDeclareCommandsPacket(self)
}
pub fn write(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
panic!("ClientboundDeclareCommandsPacket::write not implemented")
}
pub fn read<T: Read>(buf: &mut T) -> Result<GamePacket, String> {
let node_count = buf.read_varint()?;
let mut nodes = Vec::with_capacity(node_count as usize);
for _ in 0..node_count {
let node = BrigadierNodeStub::read_into(buf)?;
nodes.push(node);
}
let root_index = buf.read_varint()?;
Ok(GamePacket::ClientboundDeclareCommandsPacket(
ClientboundDeclareCommandsPacket {
entries: nodes,
root_index,
},
))
}
}
#[derive(Hash, Debug, Clone)]
pub struct BrigadierNodeStub {}
@ -82,7 +59,7 @@ impl<T: McBufWritable> McBufWritable for BrigadierNumber<T> {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, McBuf)]
pub enum BrigadierString {
/// Reads a single word
SingleWord = 0,
@ -92,24 +69,6 @@ pub enum BrigadierString {
GreedyPhrase = 2,
}
impl McBufReadable for BrigadierString {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let id = buf.read_byte()?;
Ok(match id {
0 => BrigadierString::SingleWord,
1 => BrigadierString::QuotablePhrase,
2 => BrigadierString::GreedyPhrase,
_ => panic!("Unknown BrigadierString id: {}", id),
})
}
}
impl McBufWritable for BrigadierString {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
buf.write_byte(*self as u8)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum BrigadierParser {
Bool,
@ -148,7 +107,6 @@ pub enum BrigadierParser {
MobEffect,
Function,
EntityAnchor,
Range { decimals_allowed: bool },
IntRange,
FloatRange,
ItemEnchantment,
@ -160,123 +118,79 @@ pub enum BrigadierParser {
Time,
ResourceOrTag { registry_key: ResourceLocation },
Resource { registry_key: ResourceLocation },
TemplateMirror,
TemplateRotation,
}
impl McBufReadable for BrigadierParser {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let parser = buf.read_resource_location()?;
let parser_type = u32::var_read_into(buf)?;
if parser == ResourceLocation::new("brigadier:bool")? {
Ok(BrigadierParser::Bool)
} else if parser == ResourceLocation::new("brigadier:double")? {
Ok(BrigadierParser::Double(BrigadierNumber::read_into(buf)?))
} else if parser == ResourceLocation::new("brigadier:float")? {
Ok(BrigadierParser::Float(BrigadierNumber::read_into(buf)?))
} else if parser == ResourceLocation::new("brigadier:integer")? {
Ok(BrigadierParser::Integer(BrigadierNumber::read_into(buf)?))
} else if parser == ResourceLocation::new("brigadier:long")? {
Ok(BrigadierParser::Long(BrigadierNumber::read_into(buf)?))
} else if parser == ResourceLocation::new("brigadier:string")? {
Ok(BrigadierParser::String(BrigadierString::read_into(buf)?))
} else if parser == ResourceLocation::new("minecraft:entity")? {
let flags = buf.read_byte()?;
Ok(BrigadierParser::Entity {
single: flags & 0x01 != 0,
players_only: flags & 0x02 != 0,
})
} else if parser == ResourceLocation::new("minecraft:game_profile")? {
Ok(BrigadierParser::GameProfile)
} else if parser == ResourceLocation::new("minecraft:block_pos")? {
Ok(BrigadierParser::BlockPos)
} else if parser == ResourceLocation::new("minecraft:column_pos")? {
Ok(BrigadierParser::ColumnPos)
} else if parser == ResourceLocation::new("minecraft:vec3")? {
Ok(BrigadierParser::Vec3)
} else if parser == ResourceLocation::new("minecraft:vec2")? {
Ok(BrigadierParser::Vec2)
} else if parser == ResourceLocation::new("minecraft:block_state")? {
Ok(BrigadierParser::BlockState)
} else if parser == ResourceLocation::new("minecraft:block_predicate")? {
Ok(BrigadierParser::BlockPredicate)
} else if parser == ResourceLocation::new("minecraft:item_stack")? {
Ok(BrigadierParser::ItemStack)
} else if parser == ResourceLocation::new("minecraft:item_predicate")? {
Ok(BrigadierParser::ItemPredicate)
} else if parser == ResourceLocation::new("minecraft:color")? {
Ok(BrigadierParser::Color)
} else if parser == ResourceLocation::new("minecraft:component")? {
Ok(BrigadierParser::Component)
} else if parser == ResourceLocation::new("minecraft:message")? {
Ok(BrigadierParser::Message)
} else if parser == ResourceLocation::new("minecraft:nbt")? {
Ok(BrigadierParser::Nbt)
} else if parser == ResourceLocation::new("minecraft:nbt_path")? {
Ok(BrigadierParser::NbtPath)
} else if parser == ResourceLocation::new("minecraft:objective")? {
Ok(BrigadierParser::Objective)
} else if parser == ResourceLocation::new("minecraft:objective_criteria")? {
Ok(BrigadierParser::ObjectiveCriteira)
} else if parser == ResourceLocation::new("minecraft:operation")? {
Ok(BrigadierParser::Operation)
} else if parser == ResourceLocation::new("minecraft:particle")? {
Ok(BrigadierParser::Particle)
} else if parser == ResourceLocation::new("minecraft:rotation")? {
Ok(BrigadierParser::Rotation)
} else if parser == ResourceLocation::new("minecraft:angle")? {
Ok(BrigadierParser::Angle)
} else if parser == ResourceLocation::new("minecraft:scoreboard_slot")? {
Ok(BrigadierParser::ScoreboardSlot)
} else if parser == ResourceLocation::new("minecraft:score_holder")? {
let flags = buf.read_byte()?;
Ok(BrigadierParser::ScoreHolder {
allows_multiple: flags & 0x01 != 0,
})
} else if parser == ResourceLocation::new("minecraft:swizzle")? {
Ok(BrigadierParser::Swizzle)
} else if parser == ResourceLocation::new("minecraft:team")? {
Ok(BrigadierParser::Team)
} else if parser == ResourceLocation::new("minecraft:item_slot")? {
Ok(BrigadierParser::ItemSlot)
} else if parser == ResourceLocation::new("minecraft:resource_location")? {
Ok(BrigadierParser::ResourceLocation)
} else if parser == ResourceLocation::new("minecraft:mob_effect")? {
Ok(BrigadierParser::MobEffect)
} else if parser == ResourceLocation::new("minecraft:function")? {
Ok(BrigadierParser::Function)
} else if parser == ResourceLocation::new("minecraft:entity_anchor")? {
Ok(BrigadierParser::EntityAnchor)
} else if parser == ResourceLocation::new("minecraft:range")? {
Ok(BrigadierParser::Range {
decimals_allowed: buf.read_boolean()?,
})
} else if parser == ResourceLocation::new("minecraft:int_range")? {
Ok(BrigadierParser::IntRange)
} else if parser == ResourceLocation::new("minecraft:float_range")? {
Ok(BrigadierParser::FloatRange)
} else if parser == ResourceLocation::new("minecraft:item_enchantment")? {
Ok(BrigadierParser::ItemEnchantment)
} else if parser == ResourceLocation::new("minecraft:entity_summon")? {
Ok(BrigadierParser::EntitySummon)
} else if parser == ResourceLocation::new("minecraft:dimension")? {
Ok(BrigadierParser::Dimension)
} else if parser == ResourceLocation::new("minecraft:uuid")? {
Ok(BrigadierParser::Uuid)
} else if parser == ResourceLocation::new("minecraft:nbt_tag")? {
Ok(BrigadierParser::NbtTag)
} else if parser == ResourceLocation::new("minecraft:nbt_compound_tag")? {
Ok(BrigadierParser::NbtCompoundTag)
} else if parser == ResourceLocation::new("minecraft:time")? {
Ok(BrigadierParser::Time)
} else if parser == ResourceLocation::new("minecraft:resource_or_tag")? {
Ok(BrigadierParser::ResourceOrTag {
match parser_type {
0 => Ok(BrigadierParser::Bool),
1 => Ok(BrigadierParser::Float(BrigadierNumber::read_into(buf)?)),
2 => Ok(BrigadierParser::Double(BrigadierNumber::read_into(buf)?)),
3 => Ok(BrigadierParser::Integer(BrigadierNumber::read_into(buf)?)),
4 => Ok(BrigadierParser::Long(BrigadierNumber::read_into(buf)?)),
5 => Ok(BrigadierParser::String(BrigadierString::read_into(buf)?)),
6 => {
let flags = buf.read_byte()?;
Ok(BrigadierParser::Entity {
single: flags & 0x01 != 0,
players_only: flags & 0x02 != 0,
})
}
7 => Ok(BrigadierParser::GameProfile),
8 => Ok(BrigadierParser::BlockPos),
9 => Ok(BrigadierParser::ColumnPos),
10 => Ok(BrigadierParser::Vec3),
11 => Ok(BrigadierParser::Vec2),
12 => Ok(BrigadierParser::BlockState),
13 => Ok(BrigadierParser::BlockPredicate),
14 => Ok(BrigadierParser::ItemStack),
15 => Ok(BrigadierParser::ItemPredicate),
16 => Ok(BrigadierParser::Color),
17 => Ok(BrigadierParser::Component),
18 => Ok(BrigadierParser::Message),
19 => Ok(BrigadierParser::NbtCompoundTag),
20 => Ok(BrigadierParser::NbtTag),
21 => Ok(BrigadierParser::NbtPath),
22 => Ok(BrigadierParser::Objective),
23 => Ok(BrigadierParser::ObjectiveCriteira),
24 => Ok(BrigadierParser::Operation),
25 => Ok(BrigadierParser::Particle),
26 => Ok(BrigadierParser::Angle),
27 => Ok(BrigadierParser::Rotation),
28 => Ok(BrigadierParser::ScoreboardSlot),
29 => {
let flags = buf.read_byte()?;
Ok(BrigadierParser::ScoreHolder {
allows_multiple: flags & 0x01 != 0,
})
}
30 => Ok(BrigadierParser::Swizzle),
31 => Ok(BrigadierParser::Team),
32 => Ok(BrigadierParser::ItemSlot),
33 => Ok(BrigadierParser::ResourceLocation),
34 => Ok(BrigadierParser::MobEffect),
35 => Ok(BrigadierParser::Function),
36 => Ok(BrigadierParser::EntityAnchor),
37 => Ok(BrigadierParser::IntRange),
38 => Ok(BrigadierParser::FloatRange),
39 => Ok(BrigadierParser::ItemEnchantment),
40 => Ok(BrigadierParser::EntitySummon),
41 => Ok(BrigadierParser::Dimension),
42 => Ok(BrigadierParser::Time),
43 => Ok(BrigadierParser::ResourceOrTag {
registry_key: buf.read_resource_location()?,
})
} else if parser == ResourceLocation::new("minecraft:resource")? {
Ok(BrigadierParser::Resource {
}),
44 => Ok(BrigadierParser::Resource {
registry_key: buf.read_resource_location()?,
})
} else {
panic!("Unknown Brigadier parser: {}", parser)
}),
45 => Ok(BrigadierParser::TemplateMirror),
46 => Ok(BrigadierParser::TemplateRotation),
47 => Ok(BrigadierParser::Uuid),
_ => Err(format!("Unknown BrigadierParser type: {}", parser_type)),
}
}
}
@ -287,7 +201,7 @@ impl McBufReadable for BrigadierNodeStub {
let flags = u8::read_into(buf)?;
if flags > 31 {
println!(
"Warning: The flags from a Brigadier node are over 31. This is probably a bug."
"Warning: The flags from a Brigadier node are over 31 ({flags}; {flags:#b}). This is probably a bug.",
);
}
@ -319,3 +233,9 @@ impl McBufReadable for BrigadierNodeStub {
// return Err("Unknown node type".to_string());
}
}
impl McBufWritable for BrigadierNodeStub {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
todo!()
}
}

View file

@ -1,4 +1,4 @@
use azalea_core::{game_type::GameType, resource_location::ResourceLocation};
use azalea_core::{game_type::GameType, resource_location::ResourceLocation, GlobalPos};
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
@ -9,7 +9,7 @@ pub struct ClientboundLoginPacket {
pub previous_game_type: Option<GameType>,
pub levels: Vec<ResourceLocation>,
pub registry_holder: azalea_nbt::Tag,
pub dimension_type: azalea_nbt::Tag,
pub dimension_type: ResourceLocation,
pub dimension: ResourceLocation,
pub seed: i64,
#[var]
@ -22,4 +22,5 @@ pub struct ClientboundLoginPacket {
pub show_death_screen: bool,
pub is_debug: bool,
pub is_flat: bool,
pub last_death_location: Option<GlobalPos>,
}

View file

@ -0,0 +1,21 @@
use azalea_chat::component::Component;
use azalea_crypto::SaltSignaturePair;
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundPlayerChatPacket {
pub signed_content: Component,
pub unsigned_content: Option<Component>,
#[var]
pub type_id: i32,
pub sender: ChatSender,
pub timestamp: u64,
pub salt_signature: SaltSignaturePair,
}
#[derive(Clone, Debug, McBuf)]
pub struct ChatSender {
pub uuid: uuid::Uuid,
pub name: Component,
pub team_name: Option<Component>,
}

View file

@ -0,0 +1,9 @@
use azalea_chat::component::Component;
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundServerDataPacket {
pub motd: Option<Component>,
pub icon_base64: Option<String>,
pub previews_chat: bool,
}

View file

@ -0,0 +1,6 @@
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSetDisplayChatPreviewPacket {
pub enabled: bool,
}

View file

@ -0,0 +1,9 @@
use azalea_chat::component::Component;
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSystemChatPacket {
pub content: Component,
#[var]
pub type_id: i32,
}

View file

@ -1,10 +1,10 @@
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_changed_ack_packet;
pub mod clientbound_block_update_packet;
pub mod clientbound_change_difficulty_packet;
pub mod clientbound_chat_packet;
pub mod clientbound_chat_preview_packet;
pub mod clientbound_container_set_content_packet;
pub mod clientbound_custom_payload_packet;
pub mod clientbound_declare_commands_packet;
@ -23,27 +23,33 @@ 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_chat_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_section_blocks_update_packet;
pub mod clientbound_server_data_packet;
pub mod clientbound_set_carried_item_packet;
pub mod clientbound_set_chunk_cache_center;
pub mod clientbound_set_chunk_cache_center_packet;
pub mod clientbound_set_default_spawn_position_packet;
pub mod clientbound_set_display_chat_preview_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_system_chat_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_chat_command_packet;
pub mod serverbound_chat_preview_packet;
pub mod serverbound_custom_payload_packet;
pub mod serverbound_keep_alive_packet;
@ -52,55 +58,61 @@ use packet_macros::declare_state_packets;
declare_state_packets!(
GamePacket,
Serverbound => {
0x0a: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
0x0f: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
0x03: serverbound_chat_command_packet::ServerboundChatCommandPacket,
0x05: serverbound_chat_preview_packet::ServerboundChatPreviewPacket,
0x0c: serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
0x11: serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
},
Clientbound => {
0x00: clientbound_add_entity_packet::ClientboundAddEntityPacket,
0x02: clientbound_add_mob_packet::ClientboundAddMobPacket,
0x04: clientbound_add_player_packet::ClientboundAddPlayerPacket,
0x06: clientbound_animate_packet::ClientboundAnimatePacket,
0x0c: clientbound_block_update_packet::ClientboundBlockUpdatePacket,
0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
0x0f: 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,
0x1e: clientbound_game_event_packet::ClientboundGameEventPacket,
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,
0x24: clientbound_level_particles_packet::ClientboundLevelParticlesPacket,
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,
0x3f: clientbound_section_blocks_update_packet::ClientboundSectionBlocksUpdatePacket,
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,
0x02: clientbound_add_player_packet::ClientboundAddPlayerPacket,
0x03: clientbound_animate_packet::ClientboundAnimatePacket,
0x05: clientbound_block_changed_ack_packet::ClientboundBlockChangedAckPacket,
0x09: clientbound_block_update_packet::ClientboundBlockUpdatePacket,
0x0b: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
0x0c: clientbound_chat_preview_packet::ClientboundChatPreviewPacket,
0x0f: clientbound_declare_commands_packet::ClientboundDeclareCommandsPacket,
0x11: clientbound_container_set_content_packet::ClientboundContainerSetContentPacket,
0x15: clientbound_custom_payload_packet::ClientboundCustomPayloadPacket,
0x17: clientbound_disconnect_packet::ClientboundDisconnectPacket,
0x18: clientbound_entity_event_packet::ClientboundEntityEventPacket,
0x1b: clientbound_game_event_packet::ClientboundGameEventPacket,
0x1d: clientbound_initialize_border_packet::ClientboundInitializeBorderPacket,
0x1e: clientbound_keep_alive_packet::ClientboundKeepAlivePacket,
0x1f: clientbound_level_chunk_with_light_packet::ClientboundLevelChunkWithLightPacket,
0x20: clientbound_level_event_packet::ClientboundLevelEventPacket,
0x21: clientbound_level_particles_packet::ClientboundLevelParticlesPacket,
0x22: clientbound_light_update_packet::ClientboundLightUpdatePacket,
0x23: clientbound_login_packet::ClientboundLoginPacket,
0x26: clientbound_move_entity_pos_packet::ClientboundMoveEntityPosPacket,
0x27: clientbound_move_entity_posrot_packet::ClientboundMoveEntityPosRotPacket,
0x28: clientbound_move_entity_rot_packet::ClientboundMoveEntityRotPacket,
0x2f: clientbound_player_abilities_packet::ClientboundPlayerAbilitiesPacket,
0x30: clientbound_player_chat_packet::ClientboundPlayerChatPacket,
0x34: clientbound_player_info_packet::ClientboundPlayerInfoPacket,
0x36: clientbound_player_position_packet::ClientboundPlayerPositionPacket,
0x37: clientbound_recipe_packet::ClientboundRecipePacket,
0x38: clientbound_remove_entities_packet::ClientboundRemoveEntitiesPacket,
0x3c: clientbound_rotate_head_packet::ClientboundRotateHeadPacket,
0x3d: clientbound_section_blocks_update_packet::ClientboundSectionBlocksUpdatePacket,
0x3f: clientbound_server_data_packet::ClientboundServerDataPacket,
0x44: clientbound_set_entity_link_packet::ClientboundSetEntityLinkPacket,
0x47: clientbound_set_carried_item_packet::ClientboundSetCarriedItemPacket,
0x48: clientbound_set_chunk_cache_center_packet::ClientboundSetChunkCacheCenterPacket,
0x49: clientbound_update_view_distance_packet::ClientboundUpdateViewDistancePacket,
0x4a: clientbound_set_default_spawn_position_packet::ClientboundSetDefaultSpawnPositionPacket,
0x4b: clientbound_set_display_chat_preview_packet::ClientboundSetDisplayChatPreviewPacket,
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,
0x5f: clientbound_system_chat_packet::ClientboundSystemChatPacket,
0x63: clientbound_teleport_entity_packet::ClientboundTeleportEntityPacket,
0x64: clientbound_update_advancements_packet::ClientboundUpdateAdvancementsPacket,
0x65: clientbound_update_attributes_packet::ClientboundUpdateAttributesPacket,
0x67: clientbound_update_recipes_packet::ClientboundUpdateRecipesPacket,
0x68: clientbound_update_tags_packet::ClientboundUpdateTagsPacket,
}
);

View file

@ -0,0 +1,18 @@
use std::collections::HashMap;
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ServerboundChatCommandPacket {
pub command: String,
// TODO: Choose a real timestamp type
pub timestamp: u64,
pub argument_signatures: ArgumentSignatures,
pub signed_preview: bool,
}
#[derive(Clone, Debug, McBuf)]
pub struct ArgumentSignatures {
pub salt: u64,
pub signatures: HashMap<String, Vec<u8>>,
}

View file

@ -0,0 +1,7 @@
use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ServerboundChatPreviewPacket {
pub query_id: i32,
pub query: String,
}

View file

@ -1,37 +1,11 @@
use std::{
hash::Hash,
io::{Read, Write},
};
use packet_macros::LoginPacket;
use packet_macros::McBuf;
use super::LoginPacket;
use crate::mc_buf::Readable;
#[derive(Hash, Clone, Debug)]
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ClientboundHelloPacket {
// TODO: make this len thing work
// #[len(20)]
pub server_id: String,
pub public_key: Vec<u8>,
pub nonce: Vec<u8>,
}
impl ClientboundHelloPacket {
pub fn get(self) -> LoginPacket {
LoginPacket::ClientboundHelloPacket(self)
}
pub fn write(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> {
panic!("ClientboundHelloPacket::write not implemented")
}
pub fn read(buf: &mut impl Read) -> Result<LoginPacket, String> {
let server_id = buf.read_utf_with_len(20)?;
let public_key = buf.read_byte_array()?;
let nonce = buf.read_byte_array()?;
Ok(ClientboundHelloPacket {
server_id,
public_key,
nonce,
}
.get())
}
}

View file

@ -1,7 +1,18 @@
use packet_macros::{LoginPacket, McBuf};
use std::hash::Hash;
#[derive(Hash, Clone, Debug, McBuf, LoginPacket)]
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ServerboundHelloPacket {
pub username: String,
pub public_key: Option<ProfilePublicKeyData>,
}
pub struct ProfilePublicKey {
pub data: ProfilePublicKeyData,
}
#[derive(Clone, Debug, McBuf)]
pub struct ProfilePublicKeyData {
pub expires_at: u64,
pub key: Vec<u8>,
pub key_signature: Vec<u8>,
}

View file

@ -1,8 +1,46 @@
use azalea_crypto::SaltSignaturePair;
use packet_macros::{LoginPacket, McBuf};
use std::hash::Hash;
use std::io::{Read, Write};
#[derive(Hash, Clone, Debug, McBuf, LoginPacket)]
use crate::mc_buf::{McBufReadable, McBufWritable};
#[derive(Clone, Debug, McBuf, LoginPacket)]
pub struct ServerboundKeyPacket {
pub shared_secret: Vec<u8>,
pub nonce: Vec<u8>,
pub key_bytes: Vec<u8>,
pub nonce_or_salt_signature: NonceOrSaltSignature,
}
#[derive(Clone, Debug)]
pub enum NonceOrSaltSignature {
Nonce(Vec<u8>),
SaltSignature(SaltSignaturePair),
}
impl McBufReadable for NonceOrSaltSignature {
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let is_nonce = bool::read_into(buf)?;
if is_nonce {
Ok(NonceOrSaltSignature::Nonce(Vec::<u8>::read_into(buf)?))
} else {
Ok(NonceOrSaltSignature::SaltSignature(
SaltSignaturePair::read_into(buf)?,
))
}
}
}
impl McBufWritable for NonceOrSaltSignature {
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
match self {
NonceOrSaltSignature::Nonce(nonce) => {
bool::write_into(&true, buf)?;
nonce.write_into(buf)?;
}
NonceOrSaltSignature::SaltSignature(salt_signature) => {
bool::write_into(&false, buf)?;
salt_signature.write_into(buf)?;
}
}
Ok(())
}
}

View file

@ -12,7 +12,7 @@ use crate::{
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
pub const PROTOCOL_VERSION: u32 = 758;
pub const PROTOCOL_VERSION: u32 = 759;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive)]
pub enum ConnectionProtocol {

View file

@ -6,7 +6,7 @@ async fn main() {
println!("Hello, world!");
// let address = "95.111.249.143:10000";
let address = "192.168.2.234:50736";
let address = "localhost:65519";
// let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
// .await
// .unwrap();
@ -21,13 +21,13 @@ async fn main() {
// TODO: have a "loaded" or "ready" event that fires when all chunks are loaded
Event::Login => {}
Event::Chat(p) => {
println!("{}", p.message.to_ansi(None));
if p.message.to_ansi(None) == "<py5> ok" {
let state = client.state.lock().await;
let world = state.world.as_ref().unwrap();
let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap();
println!("block state: {:?}", c);
}
// println!("{}", p.message.to_ansi(None));
// if p.message.to_ansi(None) == "<py5> ok" {
// let state = client.state.lock().await;
// let world = state.world.as_ref().unwrap();
// let c = world.get_block_state(&BlockPos::new(5, 78, -2)).unwrap();
// println!("block state: {:?}", c);
// }
}
}
}

View file

@ -1,8 +0,0 @@
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\]`

View file

@ -1,62 +0,0 @@
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

@ -1,171 +0,0 @@
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, McBuf, {to_camel_case(state)}Packet)]')
uses.add(f'packet_macros::{{{to_camel_case(state)}Packet, McBuf}}')
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))

View file

@ -1,15 +0,0 @@
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:]

3
codegen/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
downloads
__pycache__
*.tmp

13
codegen/README.md Normal file
View file

@ -0,0 +1,13 @@
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 newpacket.py [packet id] [clientbound or serverbound] \[game/handshake/login/status\]`\
This will create a new file in the `azalea-protocol/src/packets/\[state\] directory`. You will probably have to manually fix up the auto generated code.
Migrate to a new Minecraft version:\
`python migrate.py [new version]`\
This updates all the packet ids in `azalea-protocol/src/packets/mod.rs` and creates all the new packets.

246
codegen/lib/code/packet.py Normal file
View file

@ -0,0 +1,246 @@
from .utils import burger_type_to_rust_type, write_packet_file
from ..utils import padded_hex, to_snake_case, to_camel_case
from ..mappings import Mappings
import os
def make_packet_mod_rs_line(packet_id: int, packet_class_name: str):
return f' {padded_hex(packet_id)}: {to_snake_case(packet_class_name)}::{to_camel_case(packet_class_name)},'
def fix_state(state: str):
return {'PLAY': 'game'}.get(state, state.lower())
def generate_packet(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 = fix_state(packet['state'])
if state != target_packet_state or direction != target_packet_direction:
continue
generated_packet_code = []
uses = set()
generated_packet_code.append(
f'#[derive(Clone, Debug, McBuf, {to_camel_case(state)}Packet)]')
uses.add(f'packet_macros::{{{to_camel_case(state)}Packet, McBuf}}')
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 = make_packet_mod_rs_line(
packet['id'], 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))
def set_packets(packet_ids: list[int], packet_class_names: list[str], direction: str, state: str):
assert len(packet_ids) == len(packet_class_names)
# sort the packets by id
packet_ids, packet_class_names = [list(x) for x in zip(
*sorted(zip(packet_ids, packet_class_names), key=lambda pair: pair[0]))] # type: ignore
mod_rs_dir = f'../azalea-protocol/src/packets/{state}/mod.rs'
with open(mod_rs_dir, 'r') as f:
mod_rs = f.read().splitlines()
new_mod_rs = []
required_modules = []
ignore_lines = False
for line in mod_rs:
if line.strip() == 'Serverbound => {':
new_mod_rs.append(line)
if direction == 'serverbound':
ignore_lines = True
for packet_id, packet_class_name in zip(packet_ids, packet_class_names):
new_mod_rs.append(
make_packet_mod_rs_line(packet_id, packet_class_name)
)
required_modules.append(packet_class_name)
else:
ignore_lines = False
continue
elif line.strip() == 'Clientbound => {':
new_mod_rs.append(line)
if direction == 'clientbound':
ignore_lines = True
for packet_id, packet_class_name in zip(packet_ids, packet_class_names):
new_mod_rs.append(
make_packet_mod_rs_line(packet_id, packet_class_name)
)
else:
ignore_lines = False
continue
elif line.strip() in ('}', '},'):
ignore_lines = False
elif line.strip().startswith('pub mod '):
continue
if not ignore_lines:
new_mod_rs.append(line)
# 0x00: clientbound_status_response_packet::ClientboundStatusResponsePacket,
if line.strip().startswith('0x'):
required_modules.append(
line.strip().split(':')[1].split('::')[0].strip())
for i, required_module in enumerate(required_modules):
if required_module not in mod_rs:
new_mod_rs.insert(i, f'pub mod {required_module};')
with open(mod_rs_dir, 'w') as f:
f.write('\n'.join(new_mod_rs))
def get_packets(direction: str, state: str):
mod_rs_dir = f'../azalea-protocol/src/packets/{state}/mod.rs'
with open(mod_rs_dir, 'r') as f:
mod_rs = f.read().splitlines()
in_serverbound = False
in_clientbound = False
packet_ids: list[int] = []
packet_class_names: list[str] = []
for line in 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'):
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)
packet_ids.append(line_packet_id)
packet_class_name = line.strip().split(':')[1].strip()
packet_class_names.append(packet_class_name)
return packet_ids, packet_class_names
def change_packet_ids(id_map: dict[int, int], direction: str, state: str):
existing_packet_ids, existing_packet_class_names = get_packets(
direction, state)
new_packet_ids = []
for packet_id in existing_packet_ids:
new_packet_id = id_map.get(packet_id, packet_id)
new_packet_ids.append(new_packet_id)
set_packets(new_packet_ids, existing_packet_class_names, direction, state)
def remove_packet_ids(removing_packet_ids: list[int], direction: str, state: str):
existing_packet_ids, existing_packet_class_names = get_packets(
direction, state)
new_packet_ids = []
new_packet_class_names = []
for packet_id, packet_class_name in zip(existing_packet_ids, existing_packet_class_names):
if packet_id in removing_packet_ids:
try:
os.remove(
f'../azalea-protocol/src/packets/{state}/{packet_class_name}.rs')
except:
pass
else:
new_packet_ids.append(packet_id)
new_packet_class_names.append(packet_class_name)
set_packets(new_packet_ids, new_packet_class_names, direction, state)

75
codegen/lib/code/utils.py Normal file
View file

@ -0,0 +1,75 @@
import os
# utilities specifically for codegen
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 fmt():
os.system('cd .. && cargo fmt')

View file

@ -0,0 +1,59 @@
import re
import os
README_DIR = os.path.join(os.path.dirname(__file__), '../../../README.md')
VERSION_REGEX = r'\*Currently supported Minecraft version: `(.*)`.\*'
def get_version_id() -> str:
with open(README_DIR, 'r') as f:
readme_text = f.read()
version_line_match = re.search(VERSION_REGEX, readme_text)
if version_line_match:
version_id = version_line_match.group(1)
return version_id
else:
raise Exception('Could not find version id in README.md')
def set_version_id(version_id: str) -> None:
with open(README_DIR, 'r') as f:
readme_text = f.read()
version_line_match = re.search(VERSION_REGEX, readme_text)
if version_line_match:
readme_text = readme_text.replace(
version_line_match.group(1), version_id)
else:
raise Exception('Could not find version id in README.md')
with open(README_DIR, 'w') as f:
f.write(readme_text)
def get_protocol_version() -> str:
# azalea-protocol/src/packets/mod.rs
# pub const PROTOCOL_VERSION: u32 = 758;
with open('../azalea-protocol/src/packets/mod.rs', 'r') as f:
mod_rs = f.read().splitlines()
for line in mod_rs:
if line.strip().startswith('pub const PROTOCOL_VERSION'):
return line.strip().split(' ')[-1].strip(';')
raise Exception(
'Could not find protocol version in azalea-protocol/src/packets/mod.rs')
def set_protocol_version(protocol_version: str) -> None:
with open('../azalea-protocol/src/packets/mod.rs', 'r') as f:
mod_rs = f.read().splitlines()
for i, line in enumerate(mod_rs):
if line.strip().startswith('pub const PROTOCOL_VERSION'):
mod_rs[i] = f'pub const PROTOCOL_VERSION: u32 = {protocol_version};'
break
else:
raise Exception(
'Could not find protocol version in azalea-protocol/src/packets/mod.rs')
with open('../azalea-protocol/src/packets/mod.rs', 'w') as f:
f.write('\n'.join(mod_rs))

90
codegen/lib/download.py Normal file
View file

@ -0,0 +1,90 @@
from .mappings import Mappings
import requests
import json
import os
# make sure the downloads directory exists
if not os.path.exists('downloads'):
os.mkdir('downloads')
def get_burger():
if not os.path.exists('downloads/Burger'):
print('\033[92mDownloading Burger...\033[m')
os.system(
'cd downloads && git clone https://github.com/pokechu22/Burger && cd Burger && git pull')
print('\033[92mInstalling dependencies...\033[m')
os.system('cd downloads/Burger && pip install six jawa')
def get_version_manifest():
if not os.path.exists(f'downloads/version_manifest.json'):
print(
f'\033[92mDownloading version manifest...\033[m')
version_manifest_data = requests.get(
'https://launchermeta.mojang.com/mc/game/version_manifest.json').json()
with open(f'downloads/version_manifest.json', 'w') as f:
json.dump(version_manifest_data, f)
else:
with open(f'downloads/version_manifest.json', 'r') as f:
version_manifest_data = json.load(f)
return version_manifest_data
def get_version_data(version_id: str):
if not os.path.exists(f'downloads/{version_id}.json'):
version_manifest_data = get_version_manifest()
print(
f'\033[92mGetting data for \033[1m{version_id}..\033[m')
try:
package_url = next(
filter(lambda v: v['id'] == version_id, version_manifest_data['versions']))['url']
except StopIteration:
raise ValueError(
f'No version with id {version_id} found. Maybe delete downloads/version_manifest.json and try again?')
package_data = requests.get(package_url).json()
with open(f'downloads/{version_id}.json', 'w') as f:
json.dump(package_data, f)
else:
with open(f'downloads/{version_id}.json', 'r') as f:
package_data = json.load(f)
return package_data
def get_client_jar(version_id: str):
if not os.path.exists(f'downloads/client-{version_id}.jar'):
package_data = get_version_data(version_id)
print('\033[92mDownloading client jar...\033[m')
client_jar_url = package_data['downloads']['client']['url']
with open(f'downloads/client-{version_id}.jar', 'wb') as f:
f.write(requests.get(client_jar_url).content)
def get_burger_data_for_version(version_id: str):
if not os.path.exists(f'downloads/burger-{version_id}.json'):
get_burger()
get_client_jar(version_id)
os.system(
f'cd downloads/Burger && python munch.py ../client-{version_id}.jar --output ../burger-{version_id}.json'
)
with open(f'downloads/burger-{version_id}.json', 'r') as f:
return json.load(f)
def get_mappings_for_version(version_id: str):
if not os.path.exists(f'downloads/mappings-{version_id}.txt'):
package_data = get_version_data(version_id)
client_mappings_url = package_data['downloads']['client_mappings']['url']
mappings_text = requests.get(client_mappings_url).text
with open(f'downloads/mappings-{version_id}.txt', 'w') as f:
f.write(mappings_text)
else:
with open(f'downloads/mappings-{version_id}.txt', 'r') as f:
mappings_text = f.read()
return Mappings.parse(mappings_text)

46
codegen/lib/utils.py Normal file
View file

@ -0,0 +1,46 @@
import re
# utilities that could be used for things other than codegen
def to_snake_case(name: str):
s = re.sub('([A-Z])', r'_\1', name)
return s.lower().strip('_')
def to_camel_case(name: str):
s = re.sub('_([a-z])', lambda m: m.group(1).upper(), name)
return s[0].upper() + s[1:]
def padded_hex(n: int):
return f'0x{n:02x}'
class PacketIdentifier:
def __init__(self, packet_id: int, direction: str, state: str):
self.packet_id = packet_id
self.direction = direction
self.state = state
def __eq__(self, other):
return self.packet_id == other.packet_id and self.direction == other.direction and self.state == other.state
def __hash__(self):
return hash((self.packet_id, self.direction, self.state))
def __str__(self):
return f'{self.packet_id} {self.direction} {self.state}'
def __repr__(self):
return f'PacketIdentifier({self.packet_id}, {self.direction}, {self.state})'
def group_packets(packets: list[PacketIdentifier]):
packet_groups: dict[tuple[str, str], list[int]] = {}
for packet in packets:
key = (packet.direction, packet.state)
if key not in packet_groups:
packet_groups[key] = []
packet_groups[key].append(packet.packet_id)
return packet_groups

82
codegen/migrate.py Normal file
View file

@ -0,0 +1,82 @@
from lib.code.packet import fix_state
from lib.utils import PacketIdentifier, group_packets
import lib.code.utils
import lib.code.version
import lib.code.packet
import lib.download
import sys
import os
old_version_id = lib.code.version.get_version_id()
old_mappings = lib.download.get_mappings_for_version(old_version_id)
old_burger_data = lib.download.get_burger_data_for_version(old_version_id)
old_packet_list = list(old_burger_data[0]['packets']['packet'].values())
new_version_id = sys.argv[1]
new_mappings = lib.download.get_mappings_for_version(new_version_id)
new_burger_data = lib.download.get_burger_data_for_version(new_version_id)
new_packet_list = list(new_burger_data[0]['packets']['packet'].values())
old_packets: dict[PacketIdentifier, str] = {}
new_packets: dict[PacketIdentifier, str] = {}
for packet in old_packet_list:
assert packet['class'].endswith('.class')
packet_name = old_mappings.get_class(packet['class'][:-6])
old_packets[PacketIdentifier(
packet['id'], packet['direction'].lower(), fix_state(packet['state']))] = packet_name
for packet in new_packet_list:
assert packet['class'].endswith('.class')
packet_name = new_mappings.get_class(packet['class'][:-6])
new_packets[PacketIdentifier(
packet['id'], packet['direction'].lower(), fix_state(packet['state']))] = packet_name
# find removed packets
removed_packets: list[PacketIdentifier] = []
for packet, packet_name in old_packets.items():
if packet_name not in new_packets.values():
removed_packets.append(packet)
print('Removed packet:', packet, packet_name)
for (direction, state), packets in group_packets(removed_packets).items():
lib.code.packet.remove_packet_ids(packets, direction, state)
print()
# find packets that changed ids
changed_packets: dict[PacketIdentifier, int] = {}
for old_packet, old_packet_name in old_packets.items():
for new_packet, new_packet_name in new_packets.items():
if old_packet_name == new_packet_name and old_packet.direction == new_packet.direction and old_packet.state == new_packet.state and old_packet.packet_id != new_packet.packet_id:
changed_packets[old_packet] = new_packet.packet_id
print('Changed packet id:', old_packet, '->',
new_packet, f'({new_packet_name})')
break
for (direction, state), packets in group_packets(list(changed_packets.keys())).items():
id_map: dict[int, int] = {}
for old_packet_id in packets:
new_packet_id = changed_packets[PacketIdentifier(
old_packet_id, direction, state)]
id_map[old_packet_id] = new_packet_id
lib.code.packet.change_packet_ids(id_map, direction, state)
print()
# find added packets
added_packets: list[PacketIdentifier] = []
for packet, packet_name in new_packets.items():
if packet_name not in old_packets.values():
added_packets.append(packet)
print('Added packet:', packet, packet_name)
for packet in added_packets:
lib.code.packet.generate_packet(
new_burger_data[0]['packets']['packet'], new_mappings, packet.packet_id, packet.direction, packet.state)
lib.code.version.set_protocol_version(
new_burger_data[0]['version']['protocol'])
lib.code.version.set_version_id(new_version_id)
lib.code.utils.fmt()
print('Done!')

17
codegen/newpacket.py Normal file
View file

@ -0,0 +1,17 @@
from lib import download, code # type: ignore
import sys
import os
mappings = download.get_mappings_for_version('1.18.2')
burger_data = download.get_burger_data_for_version('1.18.2')
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}')
code.packetcodegen.generate_packet(burger_packets_data, mappings,
packet_id, direction, state)
code.fmt()
print('Done!')