aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore5
-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_set_entity_data_packet.rs401
-rw-r--r--data-code-generator/README.md4
-rw-r--r--data-code-generator/main.py41
-rw-r--r--data-code-generator/mappings.py60
-rw-r--r--data-code-generator/packetcodegen.py100
-rw-r--r--data-code-generator/utils.py15
10 files changed, 666 insertions, 436 deletions
diff --git a/.gitignore b/.gitignore
index ad9bfc78..67d18b48 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,8 @@
flamegraph.svg
perf.data
perf.data.old
+
+data-code-generator/Burger
+data-code-generator/client.jar
+data-code-generator/burger.json
+__pycache__
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_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/data-code-generator/README.md b/data-code-generator/README.md
new file mode 100644
index 00000000..1deeee4d
--- /dev/null
+++ b/data-code-generator/README.md
@@ -0,0 +1,4 @@
+Generate code for reading/writing packets from [Burger](https://github.com/pokechu22/Burger). The only dependency is `requests`.
+
+The directory name doesn't start with `azalea-` because it's not a Rust crate.
+
diff --git a/data-code-generator/main.py b/data-code-generator/main.py
new file mode 100644
index 00000000..50550d76
--- /dev/null
+++ b/data-code-generator/main.py
@@ -0,0 +1,41 @@
+from mappings import Mappings
+import packetcodegen
+import requests
+import json
+import os
+
+# enable this if you already have the burger.json and don't want to wait
+SKIP_BURGER = True
+
+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']['snapshot']
+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']
+
+if not SKIP_BURGER:
+ print('\033[92mDownloading Burger...\033[m')
+ r = os.system('git clone https://github.com/pokechu22/Burger')
+ os.system('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']
+packetcodegen.generate(burger_packets_data, mappings)
diff --git a/data-code-generator/mappings.py b/data-code-generator/mappings.py
new file mode 100644
index 00000000..fb3e8bda
--- /dev/null
+++ b/data-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/data-code-generator/packetcodegen.py b/data-code-generator/packetcodegen.py
new file mode 100644
index 00000000..674f97c2
--- /dev/null
+++ b/data-code-generator/packetcodegen.py
@@ -0,0 +1,100 @@
+from utils import to_snake_case, to_camel_case
+from mappings import Mappings
+
+
+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'
+ elif burger_type == 'identifier':
+ field_type_rs = 'ResourceLocation'
+ elif burger_type == 'uuid':
+ field_type_rs = 'Uuid'
+ elif burger_type == 'position':
+ field_type_rs = 'BlockPos'
+ elif burger_type == 'nbtcompound':
+ field_type_rs = 'azalea_nbt::Tag'
+ elif burger_type == 'itemstack':
+ field_type_rs = 'Slot'
+ elif burger_type == 'metadata':
+ field_type_rs = '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 generate(burger_packets, mappings: Mappings):
+ for packet in burger_packets.values():
+ direction = packet['direction'].lower() # serverbound or clientbound
+ state = packet['state'].lower()
+
+ generated_packet_code = []
+ generated_packet_code.append(
+ f'#[derive(Clone, Debug, {to_camel_case(state)}Packet)]')
+
+ obfuscated_class_name = packet['class'].split('.')[0]
+ class_name = mappings.get_class(obfuscated_class_name).split('.')[-1]
+
+ 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, 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},')
+ else:
+ generated_packet_code.append(f'// TODO: {instruction}')
+ continue
+
+ generated_packet_code.append('}')
+ print(generated_packet_code)
+ print()
diff --git a/data-code-generator/utils.py b/data-code-generator/utils.py
new file mode 100644
index 00000000..1052bb87
--- /dev/null
+++ b/data-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()
+
+
+def to_camel_case(name):
+ s = re.sub('_([a-z])', lambda m: m.group(1).upper(), name)
+ return s[0].upper() + s[1:]