diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs index ace9268..2f7496e 100644 --- a/protocol/src/protocol/mod.rs +++ b/protocol/src/protocol/mod.rs @@ -7,7 +7,9 @@ extern crate serde_json; pub mod mojang; +use nbt; use format; +use std::fmt; use std::default; use std::net::TcpStream; use std::io; @@ -21,7 +23,8 @@ use self::flate2::read::{ZlibDecoder, ZlibEncoder}; macro_rules! state_packets { ($($state:ident $stateName:ident { $($dir:ident $dirName:ident { - $($name:ident => $id:expr { + $( + $name:ident => $id:expr { $($field:ident: $field_type:ty = $(when ($cond:expr))*, )+ })* })+ @@ -29,6 +32,7 @@ macro_rules! state_packets { use protocol::*; use std::io; + #[derive(Debug)] pub enum Packet { $( $( @@ -47,9 +51,12 @@ macro_rules! state_packets { use protocol::*; use std::io; use format; + use nbt; + use types; + use item; $( - #[derive(Default)] + #[derive(Default, Debug)] pub struct $name { $(pub $field: $field_type),+, } @@ -58,7 +65,7 @@ macro_rules! state_packets { fn packet_id(&self) -> i32{ $id } - fn write(self, buf: &mut Vec) -> Result<(), io::Error> { + fn write(self, buf: &mut io::Write) -> Result<(), io::Error> { $( if true $(&& ($cond(&self)))* { try!(self.$field.write_to(buf)); @@ -76,7 +83,7 @@ macro_rules! state_packets { /// 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 io::Cursor>) -> Result, io::Error> { + pub fn packet_by_id(state: State, dir: Direction, id: i32, mut buf: &mut io::Read) -> Result, io::Error> { match state { $( State::$stateName => { @@ -115,6 +122,42 @@ pub trait Serializable { fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error>; } +impl Serializable for Vec { + fn read_from(buf: &mut io::Read) -> Result , io::Error> { + let mut v = Vec::new(); + try!(buf.read_to_end(&mut v)); + Ok(v) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + buf.write_all(&self[..]) + } +} + +impl Serializable for Option{ + fn read_from(buf: &mut io::Read) -> Result, io::Error> { + let ty = try!(buf.read_u8()); + if ty == 0 { + Result::Ok(None) + } else { + let name = try!(nbt::read_string(buf)); + let tag = try!(nbt::Tag::read_from(buf)); + Result::Ok(Some(nbt::NamedTag(name, tag))) + } + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + match *self { + Some(ref val) => { + try!(buf.write_u8(10)); + try!(nbt::write_string(buf, &val.0)); + try!(val.1.write_to(buf)); + } + None => try!(buf.write_u8(0)), + } + Result::Ok(()) + } +} + impl Serializable for Option where T : Serializable { fn read_from(buf: &mut io::Read) -> Result, io::Error> { Result::Ok(Some(try!(T::read_from(buf)))) @@ -159,46 +202,6 @@ impl Serializable for format::Component { } } -pub struct Position(u64); - -impl Position { - fn new(x: i32, y: i32, z: i32) -> Position { - Position( - (((x as u64) & 0x3FFFFFF) << 38) | - (((y as u64) & 0xFFF) << 26) | - ((z as u64) & 0x3FFFFFF) - ) - } - - fn get_x(&self) -> i32 { - ((self.0 as i64) >> 38) as i32 - } - - fn get_y(&self) -> i32 { - (((self.0 as i64) >> 26) & 0xFFF) as i32 - } - - fn get_z(&self) -> i32 { - ((self.0 as i64) << 38 >> 38) as i32 - } -} - -impl Default for Position { - fn default() -> Position { - Position(0) - } -} - -impl Serializable for Position { - fn read_from(buf: &mut io::Read) -> Result { - Result::Ok(Position(try!(buf.read_u64::()))) - } - fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { - try!(buf.write_u64::(self.0)); - Result::Ok(()) - } -} - impl Serializable for () { fn read_from(_: &mut io::Read) -> Result<(), io::Error> { Result::Ok(()) @@ -218,6 +221,16 @@ impl Serializable for bool { } } +impl Serializable for i8 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_i8())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_i8(*self)); + Result::Ok(()) + } +} + impl Serializable for i16 { fn read_from(buf: &mut io::Read) -> Result { Result::Ok(try!(buf.read_i16::())) @@ -268,7 +281,54 @@ impl Serializable for u16 { } } -pub trait Lengthable : Serializable + Into + From + Copy + Default {} +impl Serializable for f32 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_f32::())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_f32::(*self)); + Result::Ok(()) + } +} + +impl Serializable for f64 { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok(try!(buf.read_f64::())) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_f64::(*self)); + Result::Ok(()) + } +} + +#[derive(Debug)] +pub struct UUID(u64, u64); + +impl Default for UUID { + fn default() -> Self { UUID(0, 0) } +} + +impl Serializable for UUID { + fn read_from(buf: &mut io::Read) -> Result { + Result::Ok( + UUID( + try!(buf.read_u64::()), + try!(buf.read_u64::()), + ) + ) + } + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(buf.write_u64::(self.0)); + try!(buf.write_u64::(self.1)); + Result::Ok(()) + } +} + + +pub trait Lengthable : Serializable + Copy + Default { + fn into(self) -> usize; + fn from(usize) -> Self; +} pub struct LenPrefixed { len: L, @@ -296,7 +356,7 @@ impl Serializable for LenPrefixed { } fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { - let len_data : L = self.data.len().into(); + let len_data : L = L::from(self.data.len()); try!(len_data.write_to(buf)); let ref data = self.data; for val in data { @@ -306,6 +366,7 @@ impl Serializable for LenPrefixed { } } + impl Default for LenPrefixed { fn default() -> Self { LenPrefixed { @@ -315,12 +376,94 @@ impl Default for LenPrefixed { } } +impl fmt::Debug for LenPrefixed { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.data.fmt(f) + } +} + +// Optimization +pub struct LenPrefixedBytes { + len: L, + pub data: Vec +} + +impl LenPrefixedBytes { + fn new(data: Vec) -> LenPrefixedBytes { + return LenPrefixedBytes { + len: Default::default(), + data: data, + } + } +} + +impl Serializable for LenPrefixedBytes { + fn read_from(buf: &mut io::Read) -> Result, io::Error> { + let len_data : L = try!(Serializable::read_from(buf)); + let len : usize = len_data.into(); + let mut data : Vec = Vec::with_capacity(len); + try!(buf.take(len as u64).read_to_end(&mut data)); + Result::Ok(LenPrefixedBytes{len: len_data, data: data}) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + let len_data : L = L::from(self.data.len()); + try!(len_data.write_to(buf)); + try!(buf.write_all(&self.data[..])); + Result::Ok(()) + } +} + + +impl Default for LenPrefixedBytes { + fn default() -> Self { + LenPrefixedBytes { + len: default::Default::default(), + data: default::Default::default() + } + } +} + +impl fmt::Debug for LenPrefixedBytes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.data.fmt(f) + } +} + +impl Lengthable for i16 { + fn into(self) -> usize { + self as usize + } + + fn from(u: usize) -> i16 { + u as i16 + } +} + +impl Lengthable for i32 { + fn into(self) -> usize { + self as usize + } + + fn from(u: usize) -> i32 { + u as i32 + } +} + /// VarInt have a variable size (between 1 and 5 bytes) when encoded based /// on the size of the number #[derive(Clone, Copy)] -pub struct VarInt(i32); +pub struct VarInt(pub i32); -impl Lengthable for VarInt {} +impl Lengthable for VarInt { + fn into(self) -> usize { + self.0 as usize + } + + fn from(u: usize) -> VarInt { + VarInt(u as i32) + } +} impl Serializable for VarInt { /// Decodes a VarInt from the Reader @@ -333,7 +476,7 @@ impl Serializable for VarInt { val |= (b & PART) << (size * 7); size+=1; if size > 5 { - return Result::Err(io::Error::new(io::ErrorKind::InvalidInput, Error::Err("VarInt too big".to_string()))) + return Result::Err(io::Error::new(io::ErrorKind::InvalidInput, Error::Err("VarInt too big".to_owned()))) } if (b & 0x80) == 0 { break @@ -362,15 +505,70 @@ impl default::Default for VarInt { fn default() -> VarInt { VarInt(0) } } -impl convert::Into for VarInt { - fn into(self) -> usize { - self.0 as usize +impl fmt::Debug for VarInt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) } } -impl convert::From for VarInt { - fn from(u: usize) -> VarInt { - VarInt(u as i32) +/// VarLong have a variable size (between 1 and 10 bytes) when encoded based +/// on the size of the number +#[derive(Clone, Copy)] +pub struct VarLong(pub i64); + +impl Lengthable for VarLong { + fn into(self) -> usize { + self.0 as usize + } + + fn from(u: usize) -> VarLong { + VarLong(u as i64) + } +} + +impl Serializable for VarLong { + /// Decodes a VarLong from the Reader + fn read_from(buf: &mut io::Read) -> Result { + const PART : u64 = 0x7F; + let mut size = 0; + let mut val = 0u64; + loop { + let b = try!(buf.read_u8()) as u64; + val |= (b & PART) << (size * 7); + size+=1; + if size > 10 { + return Result::Err(io::Error::new(io::ErrorKind::InvalidInput, Error::Err("VarLong too big".to_owned()))) + } + if (b & 0x80) == 0 { + break + } + } + + Result::Ok(VarLong(val as i64)) + } + + /// Encodes a VarLong into the Writer + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + const PART : u64 = 0x7F; + let mut val = self.0 as u64; + loop { + if (val & !PART) == 0 { + try!(buf.write_u8(val as u8)); + return Result::Ok(()); + } + try!(buf.write_u8(((val & PART) | 0x80) as u8)); + val >>= 7; + } + } +} + +impl default::Default for VarLong { + fn default() -> VarLong { VarLong(0) } +} + +impl fmt::Debug for VarLong { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) } } @@ -516,13 +714,12 @@ impl Conn { Some(val) => { let pos = buf.position() as usize; let ibuf = buf.into_inner(); - if ibuf.len() < pos { + if ibuf.len() != pos { return Result::Err(Error::Err(format!("Failed to read all of packet 0x{:X}, had {} bytes left", id, ibuf.len() - pos))) } Result::Ok(val) }, - // FIXME - None => Result::Ok(packet::Packet::StatusRequest(packet::status::serverbound::StatusRequest{empty:()}))//Result::Err(Error::Err("missing packet".to_string())) + None => Result::Err(Error::Err("missing packet".to_owned())) } } @@ -592,25 +789,25 @@ impl Clone for Conn { pub trait PacketType { fn packet_id(&self) -> i32; - fn write(self, buf: &mut Vec) -> Result<(), io::Error>; + fn write(self, buf: &mut io::Write) -> Result<(), io::Error>; } -#[test] -fn test() { +// #[test] +pub fn test() { let mut c = Conn::new("localhost:25565").unwrap(); c.write_packet(packet::handshake::serverbound::Handshake{ - protocol_version: VarInt(69), - host: "localhost".to_string(), + protocol_version: VarInt(71), + host: "localhost".to_owned(), port: 25565, next: VarInt(2), }).unwrap(); c.state = State::Login; - c.write_packet(packet::login::serverbound::LoginStart{username: "Think".to_string()}).unwrap(); + c.write_packet(packet::login::serverbound::LoginStart{username: "Think".to_owned()}).unwrap(); let packet = match c.read_packet().unwrap() { packet::Packet::EncryptionRequest(val) => val, - _ => panic!("Wrong packet"), + val => panic!("Wrong packet: {:?}", val), }; let mut key = openssl::PublicKey::new(&packet.public_key.data); @@ -620,17 +817,17 @@ fn test() { let token_e = key.encrypt(&packet.verify_token.data); let profile = mojang::Profile{ - username: "Think".to_string(), - id: "b1184d43168441cfa2128b9a3df3b6ab".to_string(), - access_token: "".to_string() + username: "Think".to_owned(), + id: "b1184d43168441cfa2128b9a3df3b6ab".to_owned(), + access_token: "".to_owned() }; profile.join_server(&packet.server_id, &shared, &packet.public_key.data); c.write_packet(packet::login::serverbound::EncryptionResponse{ - shared_secret: LenPrefixed::new(shared_e), - verify_token: LenPrefixed::new(token_e), - }); + shared_secret: LenPrefixedBytes::new(shared_e), + verify_token: LenPrefixedBytes::new(token_e), + }).unwrap(); let mut read = c.clone(); let mut write = c.clone(); @@ -660,12 +857,13 @@ fn test() { let mut count = 0; loop { match read.read_packet().unwrap() { packet::Packet::ServerMessage(val) => println!("MSG: {}", val.message), - _ => { + packet::Packet::ChunkData(_) => {}, + val => { + println!("{:?}", val); if first { - println!("got packet"); write.write_packet(packet::play::serverbound::ChatMessage{ - message: "Hello world".to_string(), - }); + message: "Hello world".to_owned(), + }).unwrap(); first = false; } count += 1; diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs index fcc07e1..f201ba6 100644 --- a/protocol/src/protocol/packet.rs +++ b/protocol/src/protocol/packet.rs @@ -1,3 +1,4 @@ +use format; state_packets!( handshake Handshaking { @@ -38,7 +39,7 @@ state_packets!( TabComplete => 0x00 { text: String =, has_target: bool =, - target: Option = when(|p: &TabComplete| p.has_target == true), + target: Option = when(|p: &TabComplete| p.has_target == true), } // ChatMessage is sent by the client when it sends a chat message or // executes a command (prefixed by '/'). @@ -76,14 +77,712 @@ state_packets!( button: u8 =, action_number: u16 =, mode: u8 =, - clicked_item: ()=, // TODO + clicked_item: Option =, // TODO + } + // CloseWindow is sent when the client closes a window. + CloseWindow => 0x07 { + id: u8 =, + } + // PluginMessageServerbound is used for custom messages between the client + // and server. This is mainly for plugins/mods but vanilla has a few channels + // registered too. + PluginMessageServerbound => 0x08 { + channel: String =, + data: Vec =, + } + // UseEntity is sent when the user interacts (right clicks) or attacks + // (left clicks) an entity. + UseEntity => 0x09 { + target_id: VarInt =, + ty: VarInt =, + target_x: f32 = when(|p: &UseEntity| p.ty.0 == 2), + target_y: f32 = when(|p: &UseEntity| p.ty.0 == 2), + target_z: f32 = when(|p: &UseEntity| p.ty.0 == 2), + hand: VarInt = when(|p: &UseEntity| p.ty.0 == 0 || p.ty.0 == 2), + } + // KeepAliveServerbound is sent by a client as a response to a + // KeepAliveClientbound. If the client doesn't reply the server + // may disconnect the client. + KeepAliveServerbound => 0x0A { + id: VarInt =, + } + // PlayerPosition is used to update the player's position. + PlayerPosition => 0x0B { + x: f64 =, + y: f64 =, + z: f64 =, + on_ground: bool =, + } + // PlayerPositionLook is a combination of PlayerPosition and + // PlayerLook. + PlayerPositionLook => 0x0C { + x: f64 =, + y: f64 =, + z: f64 =, + yaw: f32 =, + pitch: f32 =, + on_ground: bool =, + } + // PlayerLook is used to update the player's rotation. + PlayerLook => 0x0D { + yaw: f32 =, + pitch: f32 =, + on_ground: bool =, + } + // Player is used to update whether the player is on the ground or not. + Player => 0x0E { + on_ground: bool =, + } + // ClientAbilities is used to modify the players current abilities. + // Currently flying is the only one + ClientAbilities => 0x0F { + flags: u8 =, + flying_speed: f32 =, + walking_speed: f32 =, + } + // PlayerDigging is sent when the client starts/stops digging a block. + // It also can be sent for droppping items and eating/shooting. + PlayerDigging => 0x10 { + status: u8 =, + location: types::Position =, + face: u8 =, + } + // PlayerAction is sent when a player preforms various actions. + PlayerAction => 0x11 { + entity_id: VarInt =, + action_id: VarInt =, + jump_boost: VarInt =, + } + // SteerVehicle is sent by the client when steers or preforms an action + // on a vehicle. + SteerVehicle => 0x12 { + sideways: f32 =, + forward: f32 =, + flags: u8 =, + } + // ResourcePackStatus informs the server of the client's current progress + // in activating the requested resource pack + ResourcePackStatus => 0x13 { + hash: String =, + result: VarInt =, + } + // HeldItemChange is sent when the player changes the currently active + // hotbar slot. + HeldItemChange => 0x14 { + slot: i16 =, + } + // CreativeInventoryAction is sent when the client clicks in the creative + // inventory. This is used to spawn items in creative. + CreativeInventoryAction => 0x15 { + slot: i16 =, + clicked_item: Option =, + } + // SetSign sets the text on a sign after placing it. + SetSign => 0x16 { + location: types::Position =, + line1: String =, + line2: String =, + line3: String =, + line4: String =, + } + // ArmSwing is sent by the client when the player left clicks (to swing their + // arm). + ArmSwing => 0x17 { + hand: VarInt =, + } + // SpectateTeleport is sent by clients in spectator mode to teleport to a player. + SpectateTeleport => 0x18 { + target: UUID =, + } + // PlayerBlockPlacement is sent when the client tries to place a block. + PlayerBlockPlacement => 0x19 { + location: types::Position =, + face: VarInt =, + hand: VarInt =, + cursor_x: u8 =, + cursor_y: u8 =, + cursor_z: u8 =, + } + // UseItem is sent when the client tries to use an item. + UseItem => 0x1A { + hand: VarInt =, } } clientbound Clientbound { - ServerMessage => 15 { + // SpawnObject is used to spawn an object or vehicle into the world when it + // is in range of the client. + SpawnObject => 0x00 { + entity_id: VarInt =, + uuid: UUID =, + ty: u8 =, + x: i32 =, + y: i32 =, + z: i32 =, + pitch: i8 =, + yaw: i8 =, + data: i32 =, + velocity_x: i16 =, + velocity_y: i16 =, + velocity_z: i16 =, + } + // SpawnExperienceOrb spawns a single experience orb into the world when + // it is in range of the client. The count controls the amount of experience + // gained when collected. + SpawnExperienceOrb => 0x01 { + entity_id: VarInt =, + x: i32 =, + y: i32 =, + z: i32 =, + count: i16 =, + } + // SpawnGlobalEntity spawns an entity which is visible from anywhere in the + // world. Currently only used for lightning. + SpawnGlobalEntity => 0x02 { + entity_id: VarInt =, + ty: u8 =, + x: i32 =, + y: i32 =, + z: i32 =, + } + // SpawnMob is used to spawn a living entity into the world when it is in + // range of the client. + SpawnMob => 0x03 { + entity_id: VarInt =, + uuid: UUID =, + ty: u8 =, + x: i32 =, + y: i32 =, + z: i32 =, + yaw: i8 =, + pitch: i8 =, + head_pitch: i8 =, + velocity_x: i16 =, + velocity_y: i16 =, + velocity_z: i16 =, + metadata: types::Metadata =, + } + // SpawnPainting spawns a painting into the world when it is in range of + // the client. The title effects the size and the texture of the painting. + SpawnPainting => 0x04 { + entity_id: VarInt =, + title: String =, + location: types::Position =, + direction: u8 =, + } + // SpawnPlayer is used to spawn a player when they are in range of the client. + // This packet alone isn't enough to display the player as the skin and username + // information is in the player information packet. + SpawnPlayer => 0x05 { + entity_id: VarInt =, + uuid: UUID =, + x: i32 =, + y: i32 =, + z: i32 =, + yaw: i8 =, + pitch: i8 =, + metadata: types::Metadata =, + } + // Animation is sent by the server to play an animation on a specific entity. + Animation => 0x06 { + entity_id: VarInt =, + animation_id: u8 =, + } + // Statistics is used to update the statistics screen for the client. + Statistics => 0x07 { + statistices: LenPrefixed =, + } + // BlockBreakAnimation is used to create and update the block breaking + // animation played when a player starts digging a block. + BlockBreakAnimation => 0x08 { + entity_id: VarInt =, + location: types::Position =, + stage: i8 =, + } + // UpdateBlockEntity updates the nbt tag of a block entity in the + // world. + UpdateBlockEntity => 0x09 { + location: types::Position =, + action: u8 =, + nbt: Option =, + } + // BlockAction triggers different actions depending on the target block. + BlockAction => 0x0A { + location: types::Position =, + byte1: u8 =, + byte2: u8 =, + block_type: VarInt =, + } + // BlockChange is used to update a single block on the client. + BlockChange => 0x0B { + location: types::Position =, + block_id: VarInt =, + } + // BossBar displays and/or changes a boss bar that is displayed on the + // top of the client's screen. This is normally used for bosses such as + // the ender dragon or the wither. + BossBar => 0x0C { + uuid: UUID =, + action: VarInt =, + title: format::Component = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 3), + health: f32 = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 2), + color: VarInt = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 4), + style: VarInt = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 4), + flags: u8 = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 5), + } + // ServerDifficulty changes the displayed difficulty in the client's menu + // as well as some ui changes for hardcore. + ServerDifficulty => 0x0D { + difficulty: u8 =, + } + // TabCompleteReply is sent as a reply to a tab completion request. + // The matches should be possible completions for the command/chat the + // player sent. + TabCompleteReply => 0x0E { + matches: LenPrefixed =, + } + // ServerMessage is a message sent by the server. It could be from a player + // or just a system message. The Type field controls the location the + // message is displayed at and when the message is displayed. + ServerMessage => 0x0F { message: format::Component =, + // 0 - Chat message, 1 - System message, 2 - Action bar message position: u8 =, } + // MultiBlockChange is used to update a batch of blocks in a single packet. + MultiBlockChange => 0x10 { + chunk_x: i32 =, + chunk_y: i32 =, + records: LenPrefixed =, + } + // ConfirmTransaction notifies the client whether a transaction was successful + // or failed (e.g. due to lag). + ConfirmTransaction => 0x11 { + id: u8 =, + action_number: i16 =, + accepted: bool =, + } + // WindowClose forces the client to close the window with the given id, + // e.g. a chest getting destroyed. + WindowClose => 0x12 { + id: u8 =, + } + // WindowOpen tells the client to open the inventory window of the given + // type. The ID is used to reference the instance of the window in + // other packets. + WindowOpen => 0x13 { + id: u8 =, + ty: String =, + title: format::Component =, + slot_count: u8 =, + entity_id: i32 = when(|p: &WindowOpen| p.ty == "EntityHorse"), + } + // WindowItems sets every item in a window. + WindowItems => 0x14 { + id: u8 =, + items: LenPrefixed> =, + } + // WindowProperty changes the value of a property of a window. Properties + // vary depending on the window type. + WindowProperty => 0x15 { + id: u8 =, + property: i16 =, + value: i16 =, + } + // WindowSetSlot changes an itemstack in one of the slots in a window. + WindowSetSlot => 0x16 { + id: u8 =, + property: i16 =, + item: Option =, + } + // SetCooldown disables a set item (by id) for the set number of ticks + SetCooldown => 0x17 { + item_id: VarInt =, + ticks: VarInt =, + } + // PluginMessageClientbound is used for custom messages between the client + // and server. This is mainly for plugins/mods but vanilla has a few channels + // registered too. + PluginMessageClientbound => 0x18 { + channel: String =, + data: Vec =, + } + // Disconnect causes the client to disconnect displaying the passed reason. + Disconnect => 0x19 { + reason: format::Component =, + } + // EntityAction causes an entity to preform an action based on the passed + // id. + EntityAction => 0x1A { + entity_id: i32 =, + action_id: u8 =, + } + // Explosion is sent when an explosion is triggered (tnt, creeper etc). + // This plays the effect and removes the effected blocks. + Explosion => 0x1B { + x: f32 =, + y: f32 =, + z: f32 =, + radius: f32 =, + records: LenPrefixed =, + velocity_x: f32 =, + velocity_y: f32 =, + velocity_z: f32 =, + } + // ChunkUnload tells the client to unload the chunk at the specified + // position. + ChunkUnload => 0x1C { + x: i32 =, + z: i32 =, + } + // SetCompression updates the compression threshold. + SetCompression => 0x1D { + threshold: VarInt =, + } + // ChangeGameState is used to modify the game's state like gamemode or + // weather. + ChangeGameState => 0x1E { + reason: u8 =, + value: f32 =, + } + // KeepAliveClientbound is sent by a server to check if the + // 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. + KeepAliveClientbound => 0x1F { + id: VarInt =, + } + // ChunkData sends or updates a single chunk on the client. If New is set + // then biome data should be sent too. + ChunkData => 0x20 { + chunk_x: i32 =, + chunk_z: i32 =, + new: bool =, + bitmask: VarInt =, + data: LenPrefixedBytes =, + } + // Effect plays a sound effect or particle at the target location with the + // volume (of sounds) being relative to the player's position unless + // DisableRelative is set to true. + Effect => 0x21 { + effect_id: i32 =, + location: types::Position =, + data: i32 =, + disable_relative: bool =, + } + // Particle spawns particles at the target location with the various + // modifiers. + Particle => 0x22 { + particle_id: i32 =, + long_distance: bool =, + x: f32 =, + y: f32 =, + z: f32 =, + offset_x: f32 =, + offset_y: f32 =, + offset_z: f32 =, + speed: f32 =, + count: i32 =, + data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38), + data2: VarInt = when(|p: &Particle| p.particle_id == 36), + } + // SoundEffect plays the named sound at the target location. + SoundEffect => 0x23 { + name: String =, + x: i32 =, + y: i32 =, + z: i32 =, + volume: f32 =, + pitch: u8 =, + } + // JoinGame is sent after completing the login process. This + // sets the initial state for the client. + JoinGame => 0x24 { + // The entity id the client will be referenced by + entity_id: i32 =, + // The starting gamemode of the client + gamemode: u8 =, + // The dimension the client is starting in + dimension: i8 =, + // The difficuilty setting for the server + difficulty: u8 =, + // The max number of players on the server + max_players: u8 =, + // The level type of the server + level_type: String =, + // Whether the client should reduce the amount of debug + // information it displays in F3 mode + reduced_debug_info: bool =, + } + // Maps updates a single map's contents + Maps => 0x25 { + item_damage: VarInt =, + scale: i8 =, + tracking_position: bool =, + icons: LenPrefixed =, + columns: u8 =, + rows: Option = when(|p: &Maps| p.columns > 0), + x: Option = when(|p: &Maps| p.columns > 0), + z: Option = when(|p: &Maps| p.columns > 0), + data: Option> = when(|p: &Maps| p.columns > 0), + } + // EntityMove moves the entity with the id by the offsets provided. + EntityMove => 0x26 { + entity_id: VarInt =, + delta_x: i8 =, + delta_y: i8 =, + delta_z: i8 =, + on_ground: bool =, + } + // EntityLookAndMove is a combination of EntityMove and EntityLook. + EntityLookAndMove => 0x27 { + entity_id: VarInt =, + delta_x: i8 =, + delta_y: i8 =, + delta_z: i8 =, + yaw: i8 =, + pitch: i8 =, + on_ground: bool =, + } + // EntityLook rotates the entity to the new angles provided. + EntityLook => 0x28 { + entity_id: VarInt =, + yaw: i8 =, + pitch: i8 =, + on_ground: bool =, + } + // Entity does nothing. It is a result of subclassing used in Minecraft. + Entity => 0x29 { + entity_id: VarInt =, + } + // SignEditorOpen causes the client to open the editor for a sign so that + // it can write to it. Only sent in vanilla when the player places a sign. + SignEditorOpen => 0x2A { + location: types::Position =, + } + // PlayerAbilities is used to modify the players current abilities. Flying, + // creative, god mode etc. + PlayerAbilities => 0x2B { + flags: u8 =, + flying_speed: f32 =, + walking_speed: f32 =, + } + // CombatEvent is used for... you know, I never checked. I have no + // clue. + CombatEvent => 0x2C { + event: VarInt =, + direction: Option = when(|p: &CombatEvent| p.event.0 == 1), + player_id: Option = when(|p: &CombatEvent| p.event.0 == 2), + entity_id: Option = when(|p: &CombatEvent| p.event.0 == 1 || p.event.0 == 2), + message: Option = when(|p: &CombatEvent| p.event.0 == 2), + } + // PlayerInfo is sent by the server for every player connected to the server + // to provide skin and username information as well as ping and gamemode info. + PlayerInfo => 0x2D { + inner: packet::PlayerInfoData =, // Ew but watcha gonna do? + } + // TeleportPlayer is sent to change the player's position. The client is expected + // to reply to the server with the same positions as contained in this packet + // otherwise will reject future packets. + TeleportPlayer => 0x2E { + x: f64 =, + y: f64 =, + z: f64 =, + yaw: f32 =, + pitch: f32 =, + flags: u8 =, + } + // EntityUsedBed is sent by the server when a player goes to bed. + EntityUsedBed => 0x2F { + entity_id: VarInt =, + location: types::Position =, + } + // EntityDestroy destroys the entities with the ids in the provided slice. + EntityDestroy => 0x30 { + entity_ids: LenPrefixed =, + } + // EntityRemoveEffect removes an effect from an entity. + EntityRemoveEffect => 0x31 { + entity_id: VarInt =, + effect_id: i8 =, + } + // ResourcePackSend causes the client to check its cache for the requested + // resource packet and download it if its missing. Once the resource pack + // is obtained the client will use it. + ResourcePackSend => 0x32 { + url: String =, + hash: String =, + } + // Respawn is sent to respawn the player after death or when they move worlds. + Respawn => 0x33 { + dimension: i32 =, + difficulty: u8 =, + gamemode: u8 =, + level_type: String =, + } + // EntityHeadLook rotates an entity's head to the new angle. + EntityHeadLook => 0x34 { + entity_id: VarInt =, + head_yaw: i8 =, + } + // WorldBorder configures the world's border. + WorldBorder => 0x35 { + action: VarInt =, + old_radius: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1), + new_radius: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1 || p.action.0 == 0), + speed: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1), + x: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 2), + z: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 2), + portal_boundary: Option = when(|p: &WorldBorder| p.action.0 == 3), + warning_time: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 4), + warning_blocks: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 5), + } + // Camera causes the client to spectate the entity with the passed id. + // Use the player's id to de-spectate. + Camera => 0x36 { + target_id: VarInt =, + } + // SetCurrentHotbarSlot changes the player's currently selected hotbar item. + SetCurrentHotbarSlot => 0x37 { + slot: u8 =, + } + // ScoreboardDisplay is used to set the display position of a scoreboard. + ScoreboardDisplay => 0x38 { + position: u8 =, + name: String =, + } + // EntityMetadata updates the metadata for an entity. + EntityMetadata => 0x39 { + entity_id: VarInt =, + metadata: types::Metadata =, + } + // EntityAttach attaches to entities together, either by mounting or leashing. + // -1 can be used at the EntityID to deattach. + EntityAttach => 0x3A { + entity_id: i32 =, + vehicle: i32 =, + leash: bool =, + } + // EntityVelocity sets the velocity of an entity in 1/8000 of a block + // per a tick. + EntityVelocity => 0x3B { + entity_id: VarInt =, + velocity_x: i16 =, + velocity_y: i16 =, + velocity_z: i16 =, + } + // EntityEquipment is sent to display an item on an entity, like a sword + // or armor. Slot 0 is the held item and slots 1 to 4 are boots, leggings + // chestplate and helmet respectively. + EntityEquipment => 0x3C { + entity_id: VarInt =, + slot: VarInt =, + item: Option =, + } + // SetExperience updates the experience bar on the client. + SetExperience => 0x3D { + experience_bar: f32 =, + level: VarInt =, + total_experience: VarInt =, + } + // UpdateHealth is sent by the server to update the player's health and food. + UpdateHealth => 0x3E { + health: f32 =, + food: VarInt =, + food_saturation: f32 =, + } + // ScoreboardObjective creates/updates a scoreboard objective. + ScoreboardObjective => 0x3F { + name: String =, + mode: u8 =, + value: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), + ty: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), + } + // Teams creates and updates teams + Teams => 0x40 { + name: String =, + mode: u8 =, + display_name: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + prefix: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + suffix: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + flags: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + name_tag_visibility: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + collision_rule: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + color: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), + players: Option> = when(|p: &Teams| p.mode == 0 || p.mode == 3 || p.mode == 4), + } + // UpdateScore is used to update or remove an item from a scoreboard + // objective. + UpdateScore => 0x41 { + name: String =, + action: u8 =, + object_name: String =, + value: Option = when(|p: &UpdateScore| p.action != 1), + } + // SpawnPosition is sent to change the player's current spawn point. Currently + // only used by the client for the compass. + SpawnPosition => 0x42 { + location: types::Position =, + } + // TimeUpdate is sent to sync the world's time to the client, the client + // will manually tick the time itself so this doesn't need to sent repeatedly + // but if the server or client has issues keeping up this can fall out of sync + // so it is a good idea to sent this now and again + TimeUpdate => 0x43 { + world_age: i64 =, + time_of_day: i64 =, + } + // Title configures an on-screen title. + Title => 0x44 { + action: VarInt =, + title: Option = when(|p: &Title| p.action.0 == 0), + sub_title: Option = when(|p: &Title| p.action.0 == 1), + fade_in: Option = when(|p: &Title| p.action.0 == 2), + fade_stay: Option = when(|p: &Title| p.action.0 == 2), + fade_out: Option = when(|p: &Title| p.action.0 == 2), + } + // UpdateSign sets or changes the text on a sign. + UpdateSign => 0x45 { + location: types::Position =, + line1: format::Component =, + line2: format::Component =, + line3: format::Component =, + line4: format::Component =, + } + // PlayerListHeaderFooter updates the header/footer of the player list. + PlayerListHeaderFooter => 0x46 { + header: format::Component =, + footer: format::Component =, + } + // CollectItem causes the collected item to fly towards the collector. This + // does not destroy the entity. + CollectItem => 0x47 { + collected_entity_id: VarInt =, + collector_entity_id: VarInt =, + } + // EntityTeleport teleports the entity to the target location. This is + // sent if the entity moves further than EntityMove allows. + EntityTeleport => 0x48 { + entity_id: VarInt =, + x: i32 =, + y: i32 =, + z: i32 =, + yaw: i8 =, + pitch: i8 =, + on_ground: bool =, + } + // EntityProperties updates the properties for an entity. + EntityProperties => 0x49 { + entity_id: VarInt =, + properties: LenPrefixed =, + } + // EntityEffect applies a status effect to an entity for a given duration. + EntityEffect => 0x4A { + entity_id: VarInt =, + effect_id: i8 =, + amplifier: i8 =, + duration: VarInt =, + hide_particles: bool =, + } } } login Login { @@ -91,52 +790,52 @@ state_packets!( // LoginStart is sent immeditately after switching into the login // state. The passed username is used by the server to authenticate // the player in online mode. - LoginStart => 0 { + LoginStart => 0x00 { username: String =, } // EncryptionResponse is sent as a reply to EncryptionRequest. All // packets following this one must be encrypted with AES/CFB8 // encryption. - EncryptionResponse => 1 { + EncryptionResponse => 0x01 { // The key for the AES/CFB8 cipher encrypted with the // public key - shared_secret: LenPrefixed =, + shared_secret: LenPrefixedBytes =, // The verify token from the request encrypted with the // public key - verify_token: LenPrefixed =, + verify_token: LenPrefixedBytes =, } } clientbound Clientbound { // LoginDisconnect is sent by the server if there was any issues // authenticating the player during login or the general server // issues (e.g. too many players). - LoginDisconnect => 0 { + LoginDisconnect => 0x00 { reason: format::Component =, } // EncryptionRequest is sent by the server if the server is in // online mode. If it is not sent then its assumed the server is // in offline mode. - EncryptionRequest => 1 { + EncryptionRequest => 0x01 { // Generally empty, left in from legacy auth // but is still used by the client if provided server_id: String =, // A RSA Public key serialized in x.509 PRIX format - public_key: LenPrefixed =, + public_key: LenPrefixedBytes =, // Token used by the server to verify encryption is working // correctly - verify_token: LenPrefixed =, + verify_token: LenPrefixedBytes =, } // LoginSuccess is sent by the server if the player successfully // authenicates with the session servers (online mode) or straight // after LoginStart (offline mode). - LoginSuccess => 2 { + LoginSuccess => 0x02 { // String encoding of a uuid (with hyphens) uuid: String =, username: String =, } // SetInitialCompression sets the compression threshold during the // login state. - SetInitialCompression => 3 { + SetInitialCompression => 0x03 { // Threshold where a packet should be sent compressed threshold: VarInt =, } @@ -148,14 +847,14 @@ state_packets!( // switching to the Status protocol state and is used // to signal the server to send a StatusResponse to the // client - StatusRequest => 0 { + StatusRequest => 0x00 { empty: () =, } // StatusPing is sent by the client after recieving a // StatusResponse. The client uses the time from sending // the ping until the time of recieving a pong to measure // the latency between the client and the server. - StatusPing => 1 { + StatusPing => 0x01 { ping: i64 =, } } @@ -181,15 +880,268 @@ state_packets!( // "description": "Hello world", // "favicon": "data:image/png;base64," // } - StatusResponse => 0 { + StatusResponse => 0x00 { status: String =, } // StatusPong is sent as a reply to a StatusPing. // The Time field should be exactly the same as the // one sent by the client. - StatusPong => 1 { + StatusPong => 0x01 { ping: i64 =, } } } ); + +#[derive(Debug, Default)] +pub struct Statistic { + pub name: String, + pub value: VarInt, +} + +impl Serializable for Statistic { + fn read_from(buf: &mut io::Read) -> Result { + Ok(Statistic { + name: try!(Serializable::read_from(buf)), + value: try!(Serializable::read_from(buf)), + }) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(self.name.write_to(buf)); + self.value.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct BlockChangeRecord { + pub xz: u8, + pub y: u8, + pub block_id: VarInt, +} + +impl Serializable for BlockChangeRecord { + fn read_from(buf: &mut io::Read) -> Result { + Ok(BlockChangeRecord{ + xz: try!(Serializable::read_from(buf)), + y: try!(Serializable::read_from(buf)), + block_id: try!(Serializable::read_from(buf)), + }) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(self.xz.write_to(buf)); + try!(self.y.write_to(buf)); + self.block_id.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct ExplosionRecord { + pub x: i8, + pub y: i8, + pub z: i8, +} + +impl Serializable for ExplosionRecord { + fn read_from(buf: &mut io::Read) -> Result { + Ok(ExplosionRecord{ + x: try!(Serializable::read_from(buf)), + y: try!(Serializable::read_from(buf)), + z: try!(Serializable::read_from(buf)), + }) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(self.x.write_to(buf)); + try!(self.y.write_to(buf)); + self.z.write_to(buf) + } +} + +#[derive(Debug)] +pub struct MapIcon { + pub direction_type: i8, + pub x: i8, + pub z: i8, +} + +impl Serializable for MapIcon { + fn read_from(buf: &mut io::Read) -> Result { + Ok(MapIcon{ + direction_type: try!(Serializable::read_from(buf)), + x: try!(Serializable::read_from(buf)), + z: try!(Serializable::read_from(buf)), + }) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(self.direction_type.write_to(buf)); + try!(self.x.write_to(buf)); + self.z.write_to(buf) + } +} + +impl Default for MapIcon { + fn default() -> Self { + MapIcon { .. Default::default() } + } +} + +#[derive(Debug, Default)] +pub struct EntityProperty { + pub key: String, + pub value: f64, + pub modifiers: LenPrefixed, +} + +impl Serializable for EntityProperty { + fn read_from(buf: &mut io::Read) -> Result { + Ok(EntityProperty{ + key: try!(Serializable::read_from(buf)), + value: try!(Serializable::read_from(buf)), + modifiers: try!(Serializable::read_from(buf)), + }) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(self.key.write_to(buf)); + try!(self.value.write_to(buf)); + self.modifiers.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct PropertyModifier { + pub uuid: UUID, + pub amount: f64, + pub operation: i8, +} + +impl Serializable for PropertyModifier { + fn read_from(buf: &mut io::Read) -> Result { + Ok(PropertyModifier{ + uuid: try!(Serializable::read_from(buf)), + amount: try!(Serializable::read_from(buf)), + operation: try!(Serializable::read_from(buf)), + }) + } + + fn write_to(&self, buf: &mut io::Write) -> Result<(), io::Error> { + try!(self.uuid.write_to(buf)); + try!(self.amount.write_to(buf)); + self.operation.write_to(buf) + } +} + +#[derive(Debug)] +pub struct PlayerInfoData { + pub action: VarInt, + pub players: Vec, +} + +impl Serializable for PlayerInfoData { + fn read_from(buf: &mut io::Read) -> Result { + let mut m = PlayerInfoData{ + action: try!(Serializable::read_from(buf)), + players: Vec::new(), + }; + let len = try!(VarInt::read_from(buf)); + for _ in 0 .. len.0 { + let uuid = try!(UUID::read_from(buf)); + match m.action.0 { + 0 => { + let name = try!(String::read_from(buf)); + let mut props = Vec::new(); + let plen = try!(VarInt::read_from(buf)).0; + for _ in 0 .. plen { + let mut prop = PlayerProperty { + name: try!(String::read_from(buf)), + value: try!(String::read_from(buf)), + signature: Default::default(), + }; + if try!(bool::read_from(buf)) { + prop.signature = Some(try!(String::read_from(buf))); + } + props.push(prop); + } + let p = PlayerDetail::Add { + uuid: uuid, + name: name, + properties: props, + gamemode: try!(Serializable::read_from(buf)), + ping: try!(Serializable::read_from(buf)), + display: { + if try!(bool::read_from(buf)) { + Some(try!(Serializable::read_from(buf))) + } else { + None + } + }, + }; + m.players.push(p); + }, + 1 => { + m.players.push(PlayerDetail::UpdateGamemode{ + uuid: uuid, + gamemode: try!(Serializable::read_from(buf)), + }) + }, + 2 => { + m.players.push(PlayerDetail::UpdateLatency{ + uuid: uuid, + ping: try!(Serializable::read_from(buf)), + }) + }, + 3 => { + m.players.push(PlayerDetail::UpdateDisplayName{ + uuid: uuid, + display: { + if try!(bool::read_from(buf)) { + Some(try!(Serializable::read_from(buf))) + } else { + None + } + }, + }) + }, + 4 => { + m.players.push(PlayerDetail::Remove{ + uuid: uuid, + }) + }, + _ => panic!(), + } + } + Ok(m) + } + + fn write_to(&self, _: &mut io::Write) -> Result<(), io::Error> { + unimplemented!() // I'm lazy + } +} + +impl Default for PlayerInfoData { + fn default() -> Self { + PlayerInfoData { + action: VarInt(0), + players: Vec::new(), + } + } +} + +#[derive(Debug)] +pub enum PlayerDetail { + Add{uuid: UUID, name: String, properties: Vec, gamemode: VarInt, ping: VarInt, display: Option}, + UpdateGamemode{uuid: UUID, gamemode: VarInt}, + UpdateLatency{uuid: UUID, ping: VarInt}, + UpdateDisplayName{uuid: UUID, display: Option}, + Remove{uuid: UUID}, +} + +#[derive(Debug)] +pub struct PlayerProperty { + pub name: String, + pub value: String, + pub signature: Option, +}