mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
commit
d9e388d8b0
15 changed files with 787 additions and 438 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -3,3 +3,9 @@
|
|||
flamegraph.svg
|
||||
perf.data
|
||||
perf.data.old
|
||||
|
||||
code-generator/Burger
|
||||
code-generator/client.jar
|
||||
code-generator/burger.json
|
||||
__pycache__
|
||||
*.tmp
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
[workspace]
|
||||
|
||||
members = [
|
||||
"bot",
|
||||
"azalea-client",
|
||||
|
|
|
@ -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!();
|
||||
|
|
435
azalea-protocol/src/mc_buf/definitions.rs
Normal file
435
azalea-protocol/src/mc_buf/definitions.rs
Normal file
|
@ -0,0 +1,435 @@
|
|||
use crate::mc_buf::read::{McBufReadable, Readable};
|
||||
use crate::mc_buf::write::{McBufWritable, Writable};
|
||||
use azalea_chat::component::Component;
|
||||
use azalea_core::{BlockPos, Direction, Slot};
|
||||
use packet_macros::{McBufReadable, McBufWritable};
|
||||
use std::io::{Read, Write};
|
||||
use std::ops::Deref;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A Vec<u8> that isn't prefixed by a VarInt with the size.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct UnsizedByteArray(Vec<u8>);
|
||||
|
||||
impl Deref for UnsizedByteArray {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for UnsizedByteArray {
|
||||
fn from(vec: Vec<u8>) -> Self {
|
||||
Self(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for UnsizedByteArray {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.as_bytes().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents Java's BitSet, a list of bits.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, McBufReadable, McBufWritable)]
|
||||
pub struct BitSet {
|
||||
data: Vec<u64>,
|
||||
}
|
||||
|
||||
// the Index trait requires us to return a reference, but we can't do that
|
||||
impl BitSet {
|
||||
pub fn index(&self, index: usize) -> bool {
|
||||
(self.data[index / 64] & (1u64 << (index % 64))) != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub type EntityMetadata = Vec<EntityDataItem>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EntityDataItem {
|
||||
// we can't identify what the index is for here because we don't know the
|
||||
// entity type
|
||||
pub index: u8,
|
||||
pub value: EntityDataValue,
|
||||
}
|
||||
|
||||
impl McBufReadable for Vec<EntityDataItem> {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let mut metadata = Vec::new();
|
||||
loop {
|
||||
let index = buf.read_byte()?;
|
||||
if index == 0xff {
|
||||
break;
|
||||
}
|
||||
let value = EntityDataValue::read_into(buf)?;
|
||||
metadata.push(EntityDataItem { index, value });
|
||||
}
|
||||
Ok(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for Vec<EntityDataItem> {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
for item in self {
|
||||
buf.write_byte(item.index)?;
|
||||
item.value.write_into(buf)?;
|
||||
}
|
||||
buf.write_byte(0xff)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EntityDataValue {
|
||||
Byte(u8),
|
||||
// varint
|
||||
Int(i32),
|
||||
Float(f32),
|
||||
String(String),
|
||||
Component(Component),
|
||||
OptionalComponent(Option<Component>),
|
||||
ItemStack(Slot),
|
||||
Boolean(bool),
|
||||
Rotations { x: f32, y: f32, z: f32 },
|
||||
BlockPos(BlockPos),
|
||||
OptionalBlockPos(Option<BlockPos>),
|
||||
Direction(Direction),
|
||||
OptionalUuid(Option<Uuid>),
|
||||
// 0 for absent (implies air); otherwise, a block state ID as per the global palette
|
||||
// this is a varint
|
||||
OptionalBlockState(Option<i32>),
|
||||
CompoundTag(azalea_nbt::Tag),
|
||||
Particle(Particle),
|
||||
VillagerData(VillagerData),
|
||||
// 0 for absent; 1 + actual value otherwise. Used for entity IDs.
|
||||
OptionalUnsignedInt(Option<u32>),
|
||||
Pose(Pose),
|
||||
}
|
||||
|
||||
impl McBufReadable for EntityDataValue {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let type_ = buf.read_varint()?;
|
||||
Ok(match type_ {
|
||||
0 => EntityDataValue::Byte(buf.read_byte()?),
|
||||
1 => EntityDataValue::Int(buf.read_varint()?),
|
||||
2 => EntityDataValue::Float(buf.read_float()?),
|
||||
3 => EntityDataValue::String(buf.read_utf()?),
|
||||
4 => EntityDataValue::Component(Component::read_into(buf)?),
|
||||
5 => EntityDataValue::OptionalComponent(Option::<Component>::read_into(buf)?),
|
||||
6 => EntityDataValue::ItemStack(Slot::read_into(buf)?),
|
||||
7 => EntityDataValue::Boolean(buf.read_boolean()?),
|
||||
8 => EntityDataValue::Rotations {
|
||||
x: buf.read_float()?,
|
||||
y: buf.read_float()?,
|
||||
z: buf.read_float()?,
|
||||
},
|
||||
9 => EntityDataValue::BlockPos(BlockPos::read_into(buf)?),
|
||||
10 => EntityDataValue::OptionalBlockPos(Option::<BlockPos>::read_into(buf)?),
|
||||
11 => EntityDataValue::Direction(Direction::read_into(buf)?),
|
||||
12 => EntityDataValue::OptionalUuid(Option::<Uuid>::read_into(buf)?),
|
||||
13 => EntityDataValue::OptionalBlockState({
|
||||
let val = i32::read_into(buf)?;
|
||||
if val == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(val)
|
||||
}
|
||||
}),
|
||||
14 => EntityDataValue::CompoundTag(azalea_nbt::Tag::read_into(buf)?),
|
||||
15 => EntityDataValue::Particle(Particle::read_into(buf)?),
|
||||
16 => EntityDataValue::VillagerData(VillagerData::read_into(buf)?),
|
||||
17 => EntityDataValue::OptionalUnsignedInt({
|
||||
let val = buf.read_varint()?;
|
||||
if val == 0 {
|
||||
None
|
||||
} else {
|
||||
Some((val - 1) as u32)
|
||||
}
|
||||
}),
|
||||
18 => EntityDataValue::Pose(Pose::read_into(buf)?),
|
||||
_ => return Err(format!("Unknown entity data type: {}", type_)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for EntityDataValue {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, McBufReadable, McBufWritable)]
|
||||
pub enum Pose {
|
||||
Standing = 0,
|
||||
FallFlying = 1,
|
||||
Sleeping = 2,
|
||||
Swimming = 3,
|
||||
SpinAttack = 4,
|
||||
Sneaking = 5,
|
||||
LongJumping = 6,
|
||||
Dying = 7,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct VillagerData {
|
||||
#[var]
|
||||
type_: u32,
|
||||
#[var]
|
||||
profession: u32,
|
||||
#[var]
|
||||
level: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct Particle {
|
||||
#[var]
|
||||
pub id: i32,
|
||||
pub data: ParticleData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ParticleData {
|
||||
AmbientEntityEffect,
|
||||
AngryVillager,
|
||||
Block(BlockParticle),
|
||||
BlockMarker(BlockParticle),
|
||||
Bubble,
|
||||
Cloud,
|
||||
Crit,
|
||||
DamageIndicator,
|
||||
DragonBreath,
|
||||
DrippingLava,
|
||||
FallingLava,
|
||||
LandingLava,
|
||||
DrippingWater,
|
||||
FallingWater,
|
||||
Dust(DustParticle),
|
||||
DustColorTransition(DustColorTransitionParticle),
|
||||
Effect,
|
||||
ElderGuardian,
|
||||
EnchantedHit,
|
||||
Enchant,
|
||||
EndRod,
|
||||
EntityEffect,
|
||||
ExplosionEmitter,
|
||||
Explosion,
|
||||
FallingDust(BlockParticle),
|
||||
Firework,
|
||||
Fishing,
|
||||
Flame,
|
||||
SoulFireFlame,
|
||||
Soul,
|
||||
Flash,
|
||||
HappyVillager,
|
||||
Composter,
|
||||
Heart,
|
||||
InstantEffect,
|
||||
Item(ItemParticle),
|
||||
Vibration(VibrationParticle),
|
||||
ItemSlime,
|
||||
ItemSnowball,
|
||||
LargeSmoke,
|
||||
Lava,
|
||||
Mycelium,
|
||||
Note,
|
||||
Poof,
|
||||
Portal,
|
||||
Rain,
|
||||
Smoke,
|
||||
Sneeze,
|
||||
Spit,
|
||||
SquidInk,
|
||||
SweepAttack,
|
||||
TotemOfUndying,
|
||||
Underwater,
|
||||
Splash,
|
||||
Witch,
|
||||
BubblePop,
|
||||
CurrentDown,
|
||||
BubbleColumnUp,
|
||||
Nautilus,
|
||||
Dolphin,
|
||||
CampfireCozySmoke,
|
||||
CampfireSignalSmoke,
|
||||
DrippingHoney,
|
||||
FallingHoney,
|
||||
LandingHoney,
|
||||
FallingNectar,
|
||||
FallingSporeBlossom,
|
||||
Ash,
|
||||
CrimsonSpore,
|
||||
WarpedSpore,
|
||||
SporeBlossomAir,
|
||||
DrippingObsidianTear,
|
||||
FallingObsidianTear,
|
||||
LandingObsidianTear,
|
||||
ReversePortal,
|
||||
WhiteAsh,
|
||||
SmallFlame,
|
||||
Snowflake,
|
||||
DrippingDripstoneLava,
|
||||
FallingDripstoneLava,
|
||||
DrippingDripstoneWater,
|
||||
FallingDripstoneWater,
|
||||
GlowSquidInk,
|
||||
Glow,
|
||||
WaxOn,
|
||||
WaxOff,
|
||||
ElectricSpark,
|
||||
Scrape,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct BlockParticle {
|
||||
#[var]
|
||||
pub block_state: i32,
|
||||
}
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct DustParticle {
|
||||
/// Red value, 0-1
|
||||
pub red: f32,
|
||||
/// Green value, 0-1
|
||||
pub green: f32,
|
||||
/// Blue value, 0-1
|
||||
pub blue: f32,
|
||||
/// The scale, will be clamped between 0.01 and 4.
|
||||
pub scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct DustColorTransitionParticle {
|
||||
/// Red value, 0-1
|
||||
pub from_red: f32,
|
||||
/// Green value, 0-1
|
||||
pub from_green: f32,
|
||||
/// Blue value, 0-1
|
||||
pub from_blue: f32,
|
||||
/// The scale, will be clamped between 0.01 and 4.
|
||||
pub scale: f32,
|
||||
/// Red value, 0-1
|
||||
pub to_red: f32,
|
||||
/// Green value, 0-1
|
||||
pub to_green: f32,
|
||||
/// Blue value, 0-1
|
||||
pub to_blue: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct ItemParticle {
|
||||
pub item: Slot,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, McBufReadable, McBufWritable)]
|
||||
pub struct VibrationParticle {
|
||||
pub origin: BlockPos,
|
||||
pub position_type: String,
|
||||
pub block_position: BlockPos,
|
||||
#[var]
|
||||
pub entity_id: u32,
|
||||
#[var]
|
||||
pub ticks: u32,
|
||||
}
|
||||
|
||||
impl McBufReadable for ParticleData {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let id = buf.read_varint()?;
|
||||
Ok(match id {
|
||||
0 => ParticleData::AmbientEntityEffect,
|
||||
1 => ParticleData::AngryVillager,
|
||||
2 => ParticleData::Block(BlockParticle::read_into(buf)?),
|
||||
3 => ParticleData::BlockMarker(BlockParticle::read_into(buf)?),
|
||||
4 => ParticleData::Bubble,
|
||||
5 => ParticleData::Cloud,
|
||||
6 => ParticleData::Crit,
|
||||
7 => ParticleData::DamageIndicator,
|
||||
8 => ParticleData::DragonBreath,
|
||||
9 => ParticleData::DrippingLava,
|
||||
10 => ParticleData::FallingLava,
|
||||
11 => ParticleData::LandingLava,
|
||||
12 => ParticleData::DrippingWater,
|
||||
13 => ParticleData::FallingWater,
|
||||
14 => ParticleData::Dust(DustParticle::read_into(buf)?),
|
||||
15 => ParticleData::DustColorTransition(DustColorTransitionParticle::read_into(buf)?),
|
||||
16 => ParticleData::Effect,
|
||||
17 => ParticleData::ElderGuardian,
|
||||
18 => ParticleData::EnchantedHit,
|
||||
19 => ParticleData::Enchant,
|
||||
20 => ParticleData::EndRod,
|
||||
21 => ParticleData::EntityEffect,
|
||||
22 => ParticleData::ExplosionEmitter,
|
||||
23 => ParticleData::Explosion,
|
||||
24 => ParticleData::FallingDust(BlockParticle::read_into(buf)?),
|
||||
25 => ParticleData::Firework,
|
||||
26 => ParticleData::Fishing,
|
||||
27 => ParticleData::Flame,
|
||||
28 => ParticleData::SoulFireFlame,
|
||||
29 => ParticleData::Soul,
|
||||
30 => ParticleData::Flash,
|
||||
31 => ParticleData::HappyVillager,
|
||||
32 => ParticleData::Composter,
|
||||
33 => ParticleData::Heart,
|
||||
34 => ParticleData::InstantEffect,
|
||||
35 => ParticleData::Item(ItemParticle::read_into(buf)?),
|
||||
36 => ParticleData::Vibration(VibrationParticle::read_into(buf)?),
|
||||
37 => ParticleData::ItemSlime,
|
||||
38 => ParticleData::ItemSnowball,
|
||||
39 => ParticleData::LargeSmoke,
|
||||
40 => ParticleData::Lava,
|
||||
41 => ParticleData::Mycelium,
|
||||
42 => ParticleData::Note,
|
||||
43 => ParticleData::Poof,
|
||||
44 => ParticleData::Portal,
|
||||
45 => ParticleData::Rain,
|
||||
46 => ParticleData::Smoke,
|
||||
47 => ParticleData::Sneeze,
|
||||
48 => ParticleData::Spit,
|
||||
49 => ParticleData::SquidInk,
|
||||
50 => ParticleData::SweepAttack,
|
||||
51 => ParticleData::TotemOfUndying,
|
||||
52 => ParticleData::Underwater,
|
||||
53 => ParticleData::Splash,
|
||||
54 => ParticleData::Witch,
|
||||
55 => ParticleData::BubblePop,
|
||||
56 => ParticleData::CurrentDown,
|
||||
57 => ParticleData::BubbleColumnUp,
|
||||
58 => ParticleData::Nautilus,
|
||||
59 => ParticleData::Dolphin,
|
||||
60 => ParticleData::CampfireCozySmoke,
|
||||
61 => ParticleData::CampfireSignalSmoke,
|
||||
62 => ParticleData::DrippingHoney,
|
||||
63 => ParticleData::FallingHoney,
|
||||
64 => ParticleData::LandingHoney,
|
||||
65 => ParticleData::FallingNectar,
|
||||
66 => ParticleData::FallingSporeBlossom,
|
||||
67 => ParticleData::Ash,
|
||||
68 => ParticleData::CrimsonSpore,
|
||||
69 => ParticleData::WarpedSpore,
|
||||
70 => ParticleData::SporeBlossomAir,
|
||||
71 => ParticleData::DrippingObsidianTear,
|
||||
72 => ParticleData::FallingObsidianTear,
|
||||
73 => ParticleData::LandingObsidianTear,
|
||||
74 => ParticleData::ReversePortal,
|
||||
75 => ParticleData::WhiteAsh,
|
||||
76 => ParticleData::SmallFlame,
|
||||
77 => ParticleData::Snowflake,
|
||||
78 => ParticleData::DrippingDripstoneLava,
|
||||
79 => ParticleData::FallingDripstoneLava,
|
||||
80 => ParticleData::DrippingDripstoneWater,
|
||||
81 => ParticleData::FallingDripstoneWater,
|
||||
82 => ParticleData::GlowSquidInk,
|
||||
83 => ParticleData::Glow,
|
||||
84 => ParticleData::WaxOn,
|
||||
85 => ParticleData::WaxOff,
|
||||
86 => ParticleData::ElectricSpark,
|
||||
87 => ParticleData::Scrape,
|
||||
_ => return Err(format!("Unknown particle id: {}", id)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for ParticleData {
|
||||
fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
|
8
code-generator/README.md
Normal file
8
code-generator/README.md
Normal file
|
@ -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\]`
|
58
code-generator/main.py
Normal file
58
code-generator/main.py
Normal file
|
@ -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!')
|
60
code-generator/mappings.py
Normal file
60
code-generator/mappings.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
class Mappings:
|
||||
__slots__ = ('classes', 'fields', 'methods')
|
||||
|
||||
def __init__(self, classes, fields, methods):
|
||||
self.classes = classes
|
||||
self.fields = fields
|
||||
self.methods = methods
|
||||
|
||||
@staticmethod
|
||||
def parse(mappings_txt):
|
||||
classes = {}
|
||||
fields = {}
|
||||
methods = {}
|
||||
|
||||
current_obfuscated_class_name = None
|
||||
|
||||
for line in mappings_txt.splitlines():
|
||||
if line.startswith('#') or line == '':
|
||||
continue
|
||||
|
||||
if line.startswith(' '):
|
||||
# if a line starts with 4 spaces, that means it's a method or a field
|
||||
if '(' in line:
|
||||
# if it has an opening parenthesis, it's a method
|
||||
real_name_with_parameters_and_line, obfuscated_name = line.strip().split(' -> ')
|
||||
real_name_with_parameters = real_name_with_parameters_and_line.split(
|
||||
':')[-1]
|
||||
|
||||
real_name = real_name_with_parameters.split('(')[0]
|
||||
parameters = real_name_with_parameters.split('(')[1]
|
||||
|
||||
if current_obfuscated_class_name not in methods:
|
||||
methods[current_obfuscated_class_name] = {}
|
||||
methods[current_obfuscated_class_name][
|
||||
f'{obfuscated_name}({parameters})'] = real_name
|
||||
else:
|
||||
# otherwise, it's a field
|
||||
real_name_with_type, obfuscated_name = line.strip().split(' -> ')
|
||||
real_name = real_name_with_type.split(' ')[1]
|
||||
|
||||
if current_obfuscated_class_name not in fields:
|
||||
fields[current_obfuscated_class_name] = {}
|
||||
fields[current_obfuscated_class_name][obfuscated_name] = real_name
|
||||
else:
|
||||
# otherwise it's a class
|
||||
real_name, obfuscated_name = line.strip(':').split(' -> ')
|
||||
current_obfuscated_class_name = obfuscated_name
|
||||
|
||||
classes[obfuscated_name] = real_name
|
||||
|
||||
return Mappings(classes, fields, methods)
|
||||
|
||||
def get_field(self, obfuscated_class_name, obfuscated_field_name):
|
||||
return self.fields.get(obfuscated_class_name, {}).get(obfuscated_field_name)
|
||||
|
||||
def get_class(self, obfuscated_class_name):
|
||||
return self.classes[obfuscated_class_name]
|
||||
|
||||
def get_method(self, obfuscated_class_name, obfuscated_method_name, obfuscated_signature):
|
||||
return self.methods[obfuscated_class_name][f'{obfuscated_method_name}({obfuscated_signature})']
|
169
code-generator/packetcodegen.py
Normal file
169
code-generator/packetcodegen.py
Normal file
|
@ -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))
|
15
code-generator/utils.py
Normal file
15
code-generator/utils.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
import urllib.request
|
||||
import gzip
|
||||
import json
|
||||
import re
|
||||
import io
|
||||
|
||||
|
||||
def to_snake_case(name):
|
||||
s = re.sub('([A-Z])', r'_\1', name)
|
||||
return s.lower().strip('_')
|
||||
|
||||
|
||||
def to_camel_case(name):
|
||||
s = re.sub('_([a-z])', lambda m: m.group(1).upper(), name)
|
||||
return s[0].upper() + s[1:]
|
Loading…
Add table
Reference in a new issue