diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs index c0f7a6c..4a31c74 100644 --- a/protocol/src/protocol/mod.rs +++ b/protocol/src/protocol/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. #![allow(dead_code)] +#![allow(non_camel_case_types)] use aes::Aes128; use cfb8::Cfb8; @@ -36,7 +37,7 @@ use flate2::Compression; use std::time::{Instant, Duration}; use crate::shared::Position; -pub const SUPPORTED_PROTOCOL: i32 = 340; +pub const SUPPORTED_PROTOCOLS: [i32; 3] = [340, 316, 315]; /// Helper macro for defining packets @@ -83,130 +84,7 @@ macro_rules! state_packets { #[allow(non_upper_case_globals)] pub mod internal_ids { -pub const Handshake: i32 = 0x00; -pub const TeleportConfirm: i32 = 0x00; -pub const TabComplete: i32 = 0x01; -pub const ChatMessage: i32 = 0x02; -pub const ClientStatus: i32 = 0x03; -pub const ClientSettings: i32 = 0x04; -pub const ConfirmTransactionServerbound: i32 = 0x05; -pub const EnchantItem: i32 = 0x06; -pub const ClickWindow: i32 = 0x07; -pub const CloseWindow: i32 = 0x08; -pub const PluginMessageServerbound: i32 = 0x09; -pub const UseEntity: i32 = 0x0a; -pub const KeepAliveServerbound: i32 = 0x0b; -pub const Player: i32 = 0x0c; -pub const PlayerPosition: i32 = 0x0d; -pub const PlayerPositionLook: i32 = 0x0e; -pub const PlayerLook: i32 = 0x0f; -pub const VehicleMove: i32 = 0x10; -pub const SteerBoat: i32 = 0x11; -pub const CraftRecipeRequest: i32 = 0x12; -pub const ClientAbilities: i32 = 0x13; -pub const PlayerDigging: i32 = 0x14; -pub const PlayerAction: i32 = 0x15; -pub const SteerVehicle: i32 = 0x16; -pub const CraftingBookData: i32 = 0x17; -pub const ResourcePackStatus: i32 = 0x18; -pub const AdvancementTab: i32 = 0x19; -pub const HeldItemChange: i32 = 0x1a; -pub const CreativeInventoryAction: i32 = 0x1b; -pub const SetSign: i32 = 0x1c; -pub const ArmSwing: i32 = 0x1d; -pub const SpectateTeleport: i32 = 0x1e; -pub const PlayerBlockPlacement: i32 = 0x1f; -pub const UseItem: i32 = 0x20; -pub const SpawnObject: i32 = 0x00; -pub const SpawnExperienceOrb: i32 = 0x01; -pub const SpawnGlobalEntity: i32 = 0x02; -pub const SpawnMob: i32 = 0x03; -pub const SpawnPainting: i32 = 0x04; -pub const SpawnPlayer: i32 = 0x05; -pub const Animation: i32 = 0x06; -pub const Statistics: i32 = 0x07; -pub const BlockBreakAnimation: i32 = 0x08; -pub const UpdateBlockEntity: i32 = 0x09; -pub const BlockAction: i32 = 0x0a; -pub const BlockChange: i32 = 0x0b; -pub const BossBar: i32 = 0x0c; -pub const ServerDifficulty: i32 = 0x0d; -pub const TabCompleteReply: i32 = 0x0e; -pub const ServerMessage: i32 = 0x0f; -pub const MultiBlockChange: i32 = 0x10; -pub const ConfirmTransaction: i32 = 0x11; -pub const WindowClose: i32 = 0x12; -pub const WindowOpen: i32 = 0x13; -pub const WindowItems: i32 = 0x14; -pub const WindowProperty: i32 = 0x15; -pub const WindowSetSlot: i32 = 0x16; -pub const SetCooldown: i32 = 0x17; -pub const PluginMessageClientbound: i32 = 0x18; -pub const NamedSoundEffect: i32 = 0x19; -pub const Disconnect: i32 = 0x1a; -pub const EntityAction: i32 = 0x1b; -pub const Explosion: i32 = 0x1c; -pub const ChunkUnload: i32 = 0x1d; -pub const ChangeGameState: i32 = 0x1e; -pub const KeepAliveClientbound: i32 = 0x1f; -pub const ChunkData: i32 = 0x20; -pub const Effect: i32 = 0x21; -pub const Particle: i32 = 0x22; -pub const JoinGame: i32 = 0x23; -pub const Maps: i32 = 0x24; -pub const Entity: i32 = 0x25; -pub const EntityMove: i32 = 0x26; -pub const EntityLookAndMove: i32 = 0x27; -pub const EntityLook: i32 = 0x28; -pub const VehicleTeleport: i32 = 0x29; -pub const SignEditorOpen: i32 = 0x2a; -pub const CraftRecipeResponse: i32 = 0x2b; -pub const PlayerAbilities: i32 = 0x2c; -pub const CombatEvent: i32 = 0x2d; -pub const PlayerInfo: i32 = 0x2e; -pub const TeleportPlayer: i32 = 0x2f; -pub const EntityUsedBed: i32 = 0x30; -pub const UnlockRecipes: i32 = 0x31; -pub const EntityDestroy: i32 = 0x32; -pub const EntityRemoveEffect: i32 = 0x33; -pub const ResourcePackSend: i32 = 0x34; -pub const Respawn: i32 = 0x35; -pub const EntityHeadLook: i32 = 0x36; -pub const SelectAdvancementTab: i32 = 0x37; -pub const WorldBorder: i32 = 0x38; -pub const Camera: i32 = 0x39; -pub const SetCurrentHotbarSlot: i32 = 0x3a; -pub const ScoreboardDisplay: i32 = 0x3b; -pub const EntityMetadata: i32 = 0x3c; -pub const EntityAttach: i32 = 0x3d; -pub const EntityVelocity: i32 = 0x3e; -pub const EntityEquipment: i32 = 0x3f; -pub const SetExperience: i32 = 0x40; -pub const UpdateHealth: i32 = 0x41; -pub const ScoreboardObjective: i32 = 0x42; -pub const SetPassengers: i32 = 0x43; -pub const Teams: i32 = 0x44; -pub const UpdateScore: i32 = 0x45; -pub const SpawnPosition: i32 = 0x46; -pub const TimeUpdate: i32 = 0x47; -pub const Title: i32 = 0x48; -pub const SoundEffect: i32 = 0x49; -pub const PlayerListHeaderFooter: i32 = 0x4a; -pub const CollectItem: i32 = 0x4b; -pub const EntityTeleport: i32 = 0x4c; -pub const Advancements: i32 = 0x4d; -pub const EntityProperties: i32 = 0x4e; -pub const EntityEffect: i32 = 0x4f; -pub const LoginStart: i32 = 0x00; -pub const EncryptionResponse: i32 = 0x01; -pub const LoginDisconnect: i32 = 0x00; -pub const EncryptionRequest: i32 = 0x01; -pub const LoginSuccess: i32 = 0x02; -pub const SetInitialCompression: i32 = 0x03; -pub const StatusRequest: i32 = 0x00; -pub const StatusPing: i32 = 0x01; -pub const StatusResponse: i32 = 0x00; -pub const StatusPong: i32 = 0x01; + create_ids!(i32, $($name),*); } $( @@ -217,7 +95,9 @@ pub const StatusPong: i32 = 0x01; impl PacketType for $name { - fn packet_id(&self) -> i32 { internal_ids::$name } + fn packet_id(&self, version: i32) -> i32 { + packet::versions::translate_internal_packet_id_for_version(version, State::$stateName, Direction::$dirName, internal_ids::$name, false) + } fn write(self, buf: &mut W) -> Result<(), Error> { $( @@ -237,14 +117,15 @@ pub const StatusPong: i32 = 0x01; /// Returns the packet for the given state, direction and id after parsing the fields /// from the buffer. - pub fn packet_by_id(state: State, dir: Direction, id: i32, mut buf: &mut R) -> Result, Error> { + pub fn packet_by_id(version: i32, state: State, dir: Direction, id: i32, mut buf: &mut R) -> Result, Error> { match state { $( State::$stateName => { match dir { $( Direction::$dirName => { - match id { + let internal_id = packet::versions::translate_internal_packet_id_for_version(version, state, dir, id, true); + match internal_id { $( self::$state::$dir::internal_ids::$name => { use self::$state::$dir::$name; @@ -269,8 +150,52 @@ pub const StatusPong: i32 = 0x01; } } -pub mod packet; +#[macro_export] +macro_rules! protocol_packet_ids { + ($($state:ident $stateName:ident { + $($dir:ident $dirName:ident { + $( + $(#[$attr:meta])* + $id:expr => $name:ident + )* + })+ + })+) => { + use crate::protocol::*; + pub fn translate_internal_packet_id(state: State, dir: Direction, id: i32, to_internal: bool) -> i32 { + match state { + $( + State::$stateName => { + match dir { + $( + Direction::$dirName => { + if to_internal { + match id { + $( + $id => crate::protocol::packet::$state::$dir::internal_ids::$name, + )* + _ => panic!("bad packet id $id in $dir $state"), + } + } else { + match id { + $( + crate::protocol::packet::$state::$dir::internal_ids::$name => $id, + )* + _ => panic!("bad packet internal id $id in $dir $state"), + } + } + } + )* + } + } + )* + } + } + } +} + +pub mod packet; +pub mod versions; pub trait Serializable: Sized { fn read_from(buf: &mut R) -> Result; fn write_to(&self, buf: &mut W) -> Result<(), Error>; @@ -868,6 +793,7 @@ pub struct Conn { pub host: String, pub port: u16, direction: Direction, + pub protocol_version: i32, pub state: State, cipher: Option, @@ -878,7 +804,7 @@ pub struct Conn { } impl Conn { - pub fn new(target: &str) -> Result { + pub fn new(target: &str, protocol_version: i32) -> Result { // TODO SRV record support let mut parts = target.split(':').collect::>(); let address = if parts.len() == 1 { @@ -894,6 +820,7 @@ impl Conn { port: parts[1].parse().unwrap(), direction: Direction::Serverbound, state: State::Handshaking, + protocol_version, cipher: Option::None, compression_threshold: -1, compression_read: Option::None, @@ -903,7 +830,7 @@ impl Conn { pub fn write_packet(&mut self, packet: T) -> Result<(), Error> { let mut buf = Vec::new(); - VarInt(packet.packet_id()).write_to(&mut buf)?; + VarInt(packet.packet_id(self.protocol_version)).write_to(&mut buf)?; packet.write(&mut buf)?; let mut extra = if self.compression_threshold >= 0 { @@ -963,7 +890,7 @@ impl Conn { Direction::Serverbound => Direction::Clientbound, }; - let packet = packet::packet_by_id(self.state, dir, id, &mut buf)?; + let packet = packet::packet_by_id(self.protocol_version, self.state, dir, id, &mut buf)?; match packet { Some(val) => { @@ -998,7 +925,7 @@ impl Conn { let host = self.host.clone(); let port = self.port; self.write_packet(Handshake { - protocol_version: VarInt(SUPPORTED_PROTOCOL), + protocol_version: VarInt(self.protocol_version), host, port, next: VarInt(1), @@ -1131,6 +1058,7 @@ impl Clone for Conn { port: self.port, direction: self.direction, state: self.state, + protocol_version: self.protocol_version, cipher: Option::None, compression_threshold: self.compression_threshold, compression_read: Option::None, @@ -1140,7 +1068,7 @@ impl Clone for Conn { } pub trait PacketType { - fn packet_id(&self) -> i32; + fn packet_id(&self, protocol_version: i32) -> i32; fn write(self, buf: &mut W) -> Result<(), Error>; } diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs index f23e353..94a60e2 100644 --- a/protocol/src/protocol/packet.rs +++ b/protocol/src/protocol/packet.rs @@ -123,9 +123,12 @@ state_packets!( /// KeepAliveServerbound is sent by a client as a response to a /// KeepAliveClientbound. If the client doesn't reply the server /// may disconnect the client. - packet KeepAliveServerbound { + packet KeepAliveServerbound_i64 { field id: i64 =, } + packet KeepAliveServerbound_VarInt { + field id: VarInt =, + } /// PlayerPosition is used to update the player's position. packet PlayerPosition { field x: f64 =, @@ -505,9 +508,12 @@ state_packets!( /// client is still responding and keep the connection open. /// The client should reply with the KeepAliveServerbound /// packet setting ID to the same as this one. - packet KeepAliveClientbound { + packet KeepAliveClientbound_i64 { field id: i64 =, } + packet KeepAliveClientbound_VarInt { + field id: VarInt =, + } /// ChunkData sends or updates a single chunk on the client. If New is set /// then biome data should be sent too. packet ChunkData { diff --git a/protocol/src/protocol/versions.rs b/protocol/src/protocol/versions.rs new file mode 100644 index 0000000..ce355d6 --- /dev/null +++ b/protocol/src/protocol/versions.rs @@ -0,0 +1,21 @@ +use crate::protocol::*; + +mod v1_12_2; +mod v1_11_2; + +pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: Direction, id: i32, to_internal: bool) -> i32 { + match version { + // https://wiki.vg/Protocol_History + // https://wiki.vg/Protocol_version_numbers#Versions_after_the_Netty_rewrite + // 1.12.2 + 340 => v1_12_2::translate_internal_packet_id(state, dir, id, to_internal), + + // 1.11.2 + 316 => v1_11_2::translate_internal_packet_id(state, dir, id, to_internal), + + // 1.11 + 315 => v1_11_2::translate_internal_packet_id(state, dir, id, to_internal), + + _ => panic!("unsupported protocol version"), + } +} diff --git a/protocol/src/protocol/versions/v1_11_2.rs b/protocol/src/protocol/versions/v1_11_2.rs new file mode 100644 index 0000000..02dc201 --- /dev/null +++ b/protocol/src/protocol/versions/v1_11_2.rs @@ -0,0 +1,145 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => TabComplete + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => ConfirmTransactionServerbound + 0x06 => EnchantItem + 0x07 => ClickWindow + 0x08 => CloseWindow + 0x09 => PluginMessageServerbound + 0x0a => UseEntity + 0x0b => KeepAliveServerbound_VarInt + 0x0c => PlayerPosition + 0x0d => PlayerPositionLook + 0x0e => PlayerLook + 0x0f => Player + 0x10 => VehicleMove + 0x11 => SteerBoat + 0x12 => ClientAbilities + 0x13 => PlayerDigging + 0x14 => PlayerAction + 0x15 => SteerVehicle + 0x16 => ResourcePackStatus + 0x17 => HeldItemChange + 0x18 => CreativeInventoryAction + 0x19 => SetSign + 0x1a => ArmSwing + 0x1b => SpectateTeleport + 0x1c => PlayerBlockPlacement + 0x1d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob + 0x04 => SpawnPainting + 0x05 => SpawnPlayer + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange + 0x11 => ConfirmTransaction + 0x12 => WindowClose + 0x13 => WindowOpen + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => KeepAliveClientbound_VarInt + 0x20 => ChunkData + 0x21 => Effect + 0x22 => Particle + 0x23 => JoinGame + 0x24 => Maps + 0x25 => EntityMove + 0x26 => EntityLookAndMove + 0x27 => EntityLook + 0x28 => Entity + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => PlayerAbilities + 0x2c => CombatEvent + 0x2d => PlayerInfo + 0x2e => TeleportPlayer + 0x2f => EntityUsedBed + 0x30 => EntityDestroy + 0x31 => EntityRemoveEffect + 0x32 => ResourcePackSend + 0x33 => Respawn + 0x34 => EntityHeadLook + 0x35 => WorldBorder + 0x36 => Camera + 0x37 => SetCurrentHotbarSlot + 0x38 => ScoreboardDisplay + 0x39 => EntityMetadata + 0x3a => EntityAttach + 0x3b => EntityVelocity + 0x3c => EntityEquipment + 0x3d => SetExperience + 0x3e => UpdateHealth + 0x3f => ScoreboardObjective + 0x40 => SetPassengers + 0x41 => Teams + 0x42 => UpdateScore + 0x43 => SpawnPosition + 0x44 => TimeUpdate + 0x45 => Title + 0x46 => SoundEffect + 0x47 => PlayerListHeaderFooter + 0x48 => CollectItem + 0x49 => EntityTeleport + 0x4a => EntityProperties + 0x4b => EntityEffect + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_12_2.rs b/protocol/src/protocol/versions/v1_12_2.rs new file mode 100644 index 0000000..d4ceb8d --- /dev/null +++ b/protocol/src/protocol/versions/v1_12_2.rs @@ -0,0 +1,152 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => TabComplete + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => ConfirmTransactionServerbound + 0x06 => EnchantItem + 0x07 => ClickWindow + 0x08 => CloseWindow + 0x09 => PluginMessageServerbound + 0x0a => UseEntity + 0x0b => KeepAliveServerbound_i64 + 0x0c => Player + 0x0d => PlayerPosition + 0x0e => PlayerPositionLook + 0x0f => PlayerLook + 0x10 => VehicleMove + 0x11 => SteerBoat + 0x12 => CraftRecipeRequest + 0x13 => ClientAbilities + 0x14 => PlayerDigging + 0x15 => PlayerAction + 0x16 => SteerVehicle + 0x17 => CraftingBookData + 0x18 => ResourcePackStatus + 0x19 => AdvancementTab + 0x1a => HeldItemChange + 0x1b => CreativeInventoryAction + 0x1c => SetSign + 0x1d => ArmSwing + 0x1e => SpectateTeleport + 0x1f => PlayerBlockPlacement + 0x20 => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob + 0x04 => SpawnPainting + 0x05 => SpawnPlayer + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange + 0x11 => ConfirmTransaction + 0x12 => WindowClose + 0x13 => WindowOpen + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => KeepAliveClientbound_i64 + 0x20 => ChunkData + 0x21 => Effect + 0x22 => Particle + 0x23 => JoinGame + 0x24 => Maps + 0x25 => Entity + 0x26 => EntityMove + 0x27 => EntityLookAndMove + 0x28 => EntityLook + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => CraftRecipeResponse + 0x2c => PlayerAbilities + 0x2d => CombatEvent + 0x2e => PlayerInfo + 0x2f => TeleportPlayer + 0x30 => EntityUsedBed + 0x31 => UnlockRecipes + 0x32 => EntityDestroy + 0x33 => EntityRemoveEffect + 0x34 => ResourcePackSend + 0x35 => Respawn + 0x36 => EntityHeadLook + 0x37 => SelectAdvancementTab + 0x38 => WorldBorder + 0x39 => Camera + 0x3a => SetCurrentHotbarSlot + 0x3b => ScoreboardDisplay + 0x3c => EntityMetadata + 0x3d => EntityAttach + 0x3e => EntityVelocity + 0x3f => EntityEquipment + 0x40 => SetExperience + 0x41 => UpdateHealth + 0x42 => ScoreboardObjective + 0x43 => SetPassengers + 0x44 => Teams + 0x45 => UpdateScore + 0x46 => SpawnPosition + 0x47 => TimeUpdate + 0x48 => Title + 0x49 => SoundEffect + 0x4a => PlayerListHeaderFooter + 0x4b => CollectItem + 0x4c => EntityTeleport + 0x4d => Advancements + 0x4e => EntityProperties + 0x4f => EntityEffect + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + +