aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2022-05-07 23:38:16 +0000
committerGitHub <noreply@github.com>2022-05-07 23:38:16 +0000
commitd9e388d8b0d8b3bdcf7a2e21b0cd2daa78d295e7 (patch)
tree8165f83b45e06c17d9114f1672ac73f202ba4b68
parent71acb5b3363fe8a7a9ac09691de01d7342db3ad1 (diff)
parentf198bdf777d4c68b11e6aeabeb96b5035e1ec5e1 (diff)
downloadazalea-drasl-d9e388d8b0d8b3bdcf7a2e21b0cd2daa78d295e7.tar.xz
Merge pull request #5 from mat-1/code-gen
Code gen
-rwxr-xr-x.gitignore6
-rwxr-xr-xCargo.toml1
-rwxr-xr-xazalea-client/src/connect.rs6
-rw-r--r--azalea-protocol/src/mc_buf/definitions.rs435
-rwxr-xr-xazalea-protocol/src/mc_buf/mod.rs39
-rwxr-xr-xazalea-protocol/src/mc_buf/read.rs2
-rw-r--r--azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs11
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs8
-rw-r--r--azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs401
-rwxr-xr-xazalea-protocol/src/packets/game/mod.rs6
-rw-r--r--code-generator/README.md8
-rw-r--r--code-generator/main.py58
-rw-r--r--code-generator/mappings.py60
-rw-r--r--code-generator/packetcodegen.py169
-rw-r--r--code-generator/utils.py15
15 files changed, 787 insertions, 438 deletions
diff --git a/.gitignore b/.gitignore
index ad9bfc78..0c2b742a 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,9 @@
flamegraph.svg
perf.data
perf.data.old
+
+code-generator/Burger
+code-generator/client.jar
+code-generator/burger.json
+__pycache__
+*.tmp
diff --git a/Cargo.toml b/Cargo.toml
index 1c9c754b..d17c7515 100755
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,4 @@
[workspace]
-
members = [
"bot",
"azalea-client",
diff --git a/azalea-client/src/connect.rs b/azalea-client/src/connect.rs
index 15d6df4c..e35024e0 100755
--- a/azalea-client/src/connect.rs
+++ b/azalea-client/src/connect.rs
@@ -264,6 +264,12 @@ impl Client {
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);
+ }
_ => panic!("Unexpected packet {:?}", packet),
}
println!();
diff --git a/azalea-protocol/src/mc_buf/definitions.rs b/azalea-protocol/src/mc_buf/definitions.rs
new file mode 100644
index 00000000..a59fc574
--- /dev/null
+++ b/azalea-protocol/src/mc_buf/definitions.rs
@@ -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!()
+ }
+}
diff --git a/azalea-protocol/src/mc_buf/mod.rs b/azalea-protocol/src/mc_buf/mod.rs
index 1626d163..bee269c9 100755
--- a/azalea-protocol/src/mc_buf/mod.rs
+++ b/azalea-protocol/src/mc_buf/mod.rs
@@ -1,8 +1,10 @@
//! 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, McBufVarReadable, Readable};
use std::ops::Deref;
@@ -14,43 +16,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::*;
diff --git a/azalea-protocol/src/mc_buf/read.rs b/azalea-protocol/src/mc_buf/read.rs
index 72b99222..e67cfa3c 100755
--- a/azalea-protocol/src/mc_buf/read.rs
+++ b/azalea-protocol/src/mc_buf/read.rs
@@ -279,7 +279,7 @@ impl McBufVarReadable for 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())
}
}
diff --git a/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs b/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs
new file mode 100644
index 00000000..e46f5af1
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_container_set_content_packet.rs
@@ -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: i32,
+ pub items: Vec<Slot>,
+ pub carried_item: Slot,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs
new file mode 100644
index 00000000..dad050cc
--- /dev/null
+++ b/azalea-protocol/src/packets/game/clientbound_set_default_spawn_position_packet.rs
@@ -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,
+}
diff --git a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
index 03df8e13..752b7e6a 100644
--- a/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
+++ b/azalea-protocol/src/packets/game/clientbound_set_entity_data_packet.rs
@@ -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 {
#[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 {
- #[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!()
- }
+ pub metadata: EntityMetadata,
}
diff --git a/azalea-protocol/src/packets/game/mod.rs b/azalea-protocol/src/packets/game/mod.rs
index 0b717add..d112f576 100755
--- a/azalea-protocol/src/packets/game/mod.rs
+++ b/azalea-protocol/src/packets/game/mod.rs
@@ -2,6 +2,7 @@ pub mod clientbound_add_entity_packet;
pub mod clientbound_add_mob_packet;
pub mod clientbound_add_player_packet;
pub mod clientbound_change_difficulty_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;
@@ -17,6 +18,7 @@ pub mod clientbound_player_position_packet;
pub mod clientbound_recipe_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_time_packet;
@@ -39,6 +41,7 @@ declare_state_packets!(
0x04: clientbound_add_player_packet::ClientboundAddPlayerPacket,
0x0e: clientbound_change_difficulty_packet::ClientboundChangeDifficultyPacket,
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,
@@ -53,12 +56,13 @@ declare_state_packets!(
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,
0x59: clientbound_set_time_packet::ClientboundSetTimePacket,
0x64: clientbound_update_attributes_packet::ClientboundUpdateAttributesPacket,
0x66: clientbound_update_recipes_packet::ClientboundUpdateRecipesPacket,
- 0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket
+ 0x67: clientbound_update_tags_packet::ClientboundUpdateTagsPacket,
}
);
diff --git a/code-generator/README.md b/code-generator/README.md
new file mode 100644
index 00000000..5b9a6139
--- /dev/null
+++ b/code-generator/README.md
@@ -0,0 +1,8 @@
+Tools for generating code.
+
+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\]`
diff --git a/code-generator/main.py b/code-generator/main.py
new file mode 100644
index 00000000..5ab3219f
--- /dev/null
+++ b/code-generator/main.py
@@ -0,0 +1,58 @@
+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[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!')
diff --git a/code-generator/mappings.py b/code-generator/mappings.py
new file mode 100644
index 00000000..fb3e8bda
--- /dev/null
+++ b/code-generator/mappings.py
@@ -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})']
diff --git a/code-generator/packetcodegen.py b/code-generator/packetcodegen.py
new file mode 100644
index 00000000..59a48535
--- /dev/null
+++ b/code-generator/packetcodegen.py
@@ -0,0 +1,169 @@
+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]
+ 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:
+ continue
+ field_name = mappings.get_field(
+ obfuscated_class_name, obfuscated_field_name)
+ if not field_name:
+ generated_packet_code.append(f'// TODO: {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))
diff --git a/code-generator/utils.py b/code-generator/utils.py
new file mode 100644
index 00000000..5336d574
--- /dev/null
+++ b/code-generator/utils.py
@@ -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:]