diff --git a/protocol/src/format.rs b/protocol/src/format.rs new file mode 100644 index 0000000..29776bd --- /dev/null +++ b/protocol/src/format.rs @@ -0,0 +1,377 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde_json; +use std::fmt; +use std::mem; + +#[derive(Debug, Clone)] +pub enum Component { + Text(TextComponent), +} + +impl Component { + + pub fn from_string(str: &str) -> Self { + let mut component; + match serde_json::from_str::(str) { + Ok(value) => component = Component::from_value(&value), + // Sometimes mojang sends a literal string, so we should interpret it literally + Err(_) => { + component = Component::Text(TextComponent::new(str)); + convert_legacy(&mut component); + }, + } + component + } + + pub fn from_value(v: &serde_json::Value) -> Self { + let mut modifier = Modifier::from_value(v); + if let Some(val) = v.as_str() { + Component::Text(TextComponent { + text: val.to_owned(), + modifier, + }) + } else if v.get("text").is_some() { + Component::Text(TextComponent::from_value(v, modifier)) + } else if v.get("translate").is_some() { + // TODO: translations + Component::Text(TextComponent::new(v.get("translate").unwrap().as_str().unwrap())) + } else { + modifier.color = Some(Color::RGB(255, 0, 0)); + Component::Text(TextComponent { + text: "UNHANDLED".to_owned(), + modifier, + }) + } + } + + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } +} + +impl fmt::Display for Component { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Component::Text(ref txt) => write!(f, "{}", txt), + } + } +} + +impl Default for Component { + fn default() -> Self { + Component::Text(TextComponent { + text: "".to_owned(), + modifier: Default::default(), + }) + } +} + +#[derive(Debug, Default, Clone)] +pub struct Modifier { + pub extra: Option>, + pub bold: Option, + pub italic: Option, + pub underlined: Option, + pub strikethrough: Option, + pub obfuscated: Option, + pub color: Option, +} + +// TODO: Missing events click/hover/insert + +impl Modifier { + pub fn from_value(v: &serde_json::Value) -> Self { + let mut m = Modifier { + bold: v.get("bold").map_or(Option::None, |v| v.as_bool()), + italic: v.get("italic").map_or(Option::None, |v| v.as_bool()), + underlined: v.get("underlined").map_or(Option::None, |v| v.as_bool()), + strikethrough: v.get("strikethrough").map_or(Option::None, |v| v.as_bool()), + obfuscated: v.get("obfuscated").map_or(Option::None, |v| v.as_bool()), + color: v.get("color") + .map_or(Option::None, |v| v.as_str()) + .map(|v| Color::from_string(&v.to_owned())), + extra: Option::None, + }; + if let Some(extra) = v.get("extra") { + if let Some(data) = extra.as_array() { + let mut ex = Vec::new(); + for e in data { + ex.push(Component::from_value(e)); + } + m.extra = Some(ex); + } + } + m + } + + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } +} + +#[derive(Debug, Clone)] +pub struct TextComponent { + pub text: String, + pub modifier: Modifier, +} + +impl TextComponent { + pub fn new(val: &str) -> TextComponent { + TextComponent { + text: val.to_owned(), + modifier: Modifier { ..Default::default() }, + } + } + + pub fn from_value(v: &serde_json::Value, modifier: Modifier) -> Self { + TextComponent { + text: v.get("text").unwrap().as_str().unwrap_or("").to_owned(), + modifier, + } + } + + pub fn to_value(&self) -> serde_json::Value { + unimplemented!() + } +} + +impl fmt::Display for TextComponent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.text)?; + if let Some(ref extra) = self.modifier.extra { + for c in extra { + write!(f, "{}", c)?; + } + } + Result::Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Color { + Black, + DarkBlue, + DarkGreen, + DarkAqua, + DarkRed, + DarkPurple, + Gold, + Gray, + DarkGray, + Blue, + Green, + Aqua, + Red, + LightPurple, + Yellow, + White, + RGB(u8, u8, u8), +} + +impl fmt::Display for Color { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +impl Color { + fn from_string(val: &str) -> Self { + match val { + "black" => Color::Black, + "dark_blue" => Color::DarkBlue, + "dark_green" => Color::DarkGreen, + "dark_aqua" => Color::DarkAqua, + "dark_red" => Color::DarkRed, + "dark_purple" => Color::DarkPurple, + "gold" => Color::Gold, + "gray" => Color::Gray, + "dark_gray" => Color::DarkGray, + "blue" => Color::Blue, + "green" => Color::Green, + "aqua" => Color::Aqua, + "red" => Color::Red, + "light_purple" => Color::LightPurple, + "yellow" => Color::Yellow, + val if val.len() == 7 && val.as_bytes()[0] == b'#' => { + let r = match u8::from_str_radix(&val[1..3], 16) { + Ok(r) => r, + Err(_) => return Color::White, + }; + let g = match u8::from_str_radix(&val[3..5], 16) { + Ok(g) => g, + Err(_) => return Color::White, + }; + let b = match u8::from_str_radix(&val[5..7], 16) { + Ok(b) => b, + Err(_) => return Color::White, + }; + Color::RGB(r, g, b) + } + "white" | _ => Color::White, + } + } + + pub fn to_string(&self) -> String { + match *self { + Color::Black => "black".to_owned(), + Color::DarkBlue => "dark_blue".to_owned(), + Color::DarkGreen => "dark_green".to_owned(), + Color::DarkAqua => "dark_aqua".to_owned(), + Color::DarkRed => "dark_red".to_owned(), + Color::DarkPurple => "dark_purple".to_owned(), + Color::Gold => "gold".to_owned(), + Color::Gray => "gray".to_owned(), + Color::DarkGray => "dark_gray".to_owned(), + Color::Blue => "blue".to_owned(), + Color::Green => "green".to_owned(), + Color::Aqua => "aqua".to_owned(), + Color::Red => "red".to_owned(), + Color::LightPurple => "light_purple".to_owned(), + Color::Yellow => "yellow".to_owned(), + Color::White => "white".to_owned(), + Color::RGB(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b), + } + } + + pub fn to_rgb(&self) -> (u8, u8, u8) { + match *self { + Color::Black => (0, 0, 0), + Color::DarkBlue => (0, 0, 170), + Color::DarkGreen => (0, 170, 0), + Color::DarkAqua => (0, 170, 170), + Color::DarkRed => (170, 0, 0), + Color::DarkPurple => (170, 0, 170), + Color::Gold => (255, 170, 0), + Color::Gray => (170, 170, 170), + Color::DarkGray => (85, 85, 85), + Color::Blue => (85, 85, 255), + Color::Green => (85, 255, 85), + Color::Aqua => (85, 255, 255), + Color::Red => (255, 85, 85), + Color::LightPurple => (255, 85, 255), + Color::Yellow => (255, 255, 85), + Color::White => (255, 255, 255), + Color::RGB(r, g, b) => (r, g, b), + } + } +} + +#[test] +fn test_color_from() { + let test = Color::from_string(&"#FF0000".to_owned()); + match test { + Color::RGB(r, g, b) => assert!(r == 255 && g == 0 && b == 0), + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"#123456".to_owned()); + match test { + Color::RGB(r, g, b) => assert!(r == 0x12 && g == 0x34 && b == 0x56), + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"red".to_owned()); + match test { + Color::Red => {} + _ => panic!("Wrong type"), + } + let test = Color::from_string(&"dark_blue".to_owned()); + match test { + Color::DarkBlue => {} + _ => panic!("Wrong type"), + } +} + +const LEGACY_CHAR: char = 'ยง'; + +pub fn convert_legacy(c: &mut Component) { + match *c { + Component::Text(ref mut txt) => { + if let Some(ref mut extra) = txt.modifier.extra.as_mut() { + for e in extra.iter_mut() { + convert_legacy(e); + } + } + if txt.text.contains(LEGACY_CHAR) { + let mut parts = Vec::new(); + let mut last = 0; + let mut current = TextComponent::new(""); + { + let mut iter = txt.text.char_indices(); + while let Some((i, c)) = iter.next() { + if c == LEGACY_CHAR { + let next = match iter.next() { + Some(val) => val, + None => break, + }; + let color_char = next.1.to_lowercase().next().unwrap(); + current.text = txt.text[last..i].to_owned(); + last = next.0 + 1; + + let mut modifier = if (color_char >= 'a' && color_char <= 'f') || + (color_char >= '0' && color_char <= '9') { + Default::default() + } else { + current.modifier.clone() + }; + + let new = TextComponent::new(""); + parts.push(Component::Text(mem::replace(&mut current, new))); + + match color_char { + '0' => modifier.color = Some(Color::Black), + '1' => modifier.color = Some(Color::DarkBlue), + '2' => modifier.color = Some(Color::DarkGreen), + '3' => modifier.color = Some(Color::DarkAqua), + '4' => modifier.color = Some(Color::DarkRed), + '5' => modifier.color = Some(Color::DarkPurple), + '6' => modifier.color = Some(Color::Gold), + '7' => modifier.color = Some(Color::Gray), + '8' => modifier.color = Some(Color::DarkGray), + '9' => modifier.color = Some(Color::Blue), + 'a' => modifier.color = Some(Color::Green), + 'b' => modifier.color = Some(Color::Aqua), + 'c' => modifier.color = Some(Color::Red), + 'd' => modifier.color = Some(Color::LightPurple), + 'e' => modifier.color = Some(Color::Yellow), + 'f' => modifier.color = Some(Color::White), + 'k' => modifier.obfuscated = Some(true), + 'l' => modifier.bold = Some(true), + 'm' => modifier.strikethrough = Some(true), + 'n' => modifier.underlined = Some(true), + 'o' => modifier.italic = Some(true), + 'r' => {} + _ => unimplemented!(), + } + + current.modifier = modifier; + } + } + } + if last < txt.text.len() { + current.text = txt.text[last..].to_owned(); + parts.push(Component::Text(current)); + } + + let old = mem::replace(&mut txt.modifier.extra, Some(parts)); + if let Some(old_extra) = old { + if let Some(ref mut extra) = txt.modifier.extra.as_mut() { + extra.extend(old_extra); + } + } + txt.text = "".to_owned(); + } + } + } +} diff --git a/protocol/src/item.rs b/protocol/src/item.rs new file mode 100644 index 0000000..8aa6ab4 --- /dev/null +++ b/protocol/src/item.rs @@ -0,0 +1,105 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::nbt; +use crate::protocol::{self, Serializable}; +use std::io; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; + +#[derive(Debug)] +pub struct Stack { + id: isize, + count: isize, + damage: Option, + tag: Option, +} + + +impl Default for Stack { + fn default() -> Stack { + Stack { + id: -1, + count: 0, + damage: None, + tag: None, + } + } +} + +impl Serializable for Option { + fn read_from(buf: &mut R) -> Result, protocol::Error> { + let protocol_version = protocol::current_protocol_version(); + + if protocol_version >= 404 { + let present = buf.read_u8()? != 0; + if !present { + return Ok(None) + } + } + + let id = if protocol_version >= 404 { + protocol::VarInt::read_from(buf)?.0 as isize + } else { + buf.read_i16::()? as isize + }; + + if id == -1 { + return Ok(None); + } + let count = buf.read_u8()? as isize; + let damage = if protocol_version >= 404 { + // 1.13.2+ stores damage in the NBT + None + } else { + Some(buf.read_i16::()? as isize) + }; + + let tag: Option = if protocol_version >= 47 { + Serializable::read_from(buf)? + } else { + // 1.7 uses a different slot data format described on https://wiki.vg/index.php?title=Slot_Data&diff=6056&oldid=4753 + let tag_size = buf.read_i16::()?; + if tag_size != -1 { + for _ in 0..tag_size { + let _ = buf.read_u8()?; + } + // TODO: decompress zlib NBT for 1.7 + None + } else { + None + } + }; + + Ok(Some(Stack { + id: id as isize, + count, + damage, + tag, + })) + } + fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { + match *self { + Some(ref val) => { + // TODO: if protocol_version >= 404, send present and id varint, no damage, for 1.13.2 + buf.write_i16::(val.id as i16)?; + buf.write_u8(val.count as u8)?; + buf.write_i16::(val.damage.unwrap_or(0) as i16)?; + // TODO: compress zlib NBT if 1.7 + val.tag.write_to(buf)?; + } + None => buf.write_i16::(-1)?, + } + Result::Ok(()) + } +} diff --git a/protocol/src/macros.rs b/protocol/src/macros.rs new file mode 100644 index 0000000..bf2b7a2 --- /dev/null +++ b/protocol/src/macros.rs @@ -0,0 +1,25 @@ + + +#[doc(hidden)] +#[macro_export] +macro_rules! create_ids { + ($t:ty, ) => (); + ($t:ty, prev($prev:ident), $name:ident) => ( + #[allow(non_upper_case_globals)] + pub const $name: $t = $prev + 1; + ); + ($t:ty, prev($prev:ident), $name:ident, $($n:ident),+) => ( + #[allow(non_upper_case_globals)] + pub const $name: $t = $prev + 1; + create_ids!($t, prev($name), $($n),+); + ); + ($t:ty, $name:ident, $($n:ident),+) => ( + #[allow(non_upper_case_globals)] + pub const $name: $t = 0; + create_ids!($t, prev($name), $($n),+); + ); + ($t:ty, $name:ident) => ( + #[allow(non_upper_case_globals)] + pub const $name: $t = 0; + ); +} diff --git a/protocol/src/nbt/mod.rs b/protocol/src/nbt/mod.rs new file mode 100644 index 0000000..37c3eca --- /dev/null +++ b/protocol/src/nbt/mod.rs @@ -0,0 +1,311 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; +use std::io; +use std::io::Read; + +use super::protocol::Serializable; +use super::protocol; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; + +#[derive(Debug, Clone)] +pub enum Tag { + End, + Byte(i8), + Short(i16), + Int(i32), + Long(i64), + Float(f32), + Double(f64), + ByteArray(Vec), + String(String), + List(Vec), + Compound(HashMap), + IntArray(Vec), + LongArray(Vec), +} + +#[derive(Debug, Clone)] +pub struct NamedTag(pub String, pub Tag); + +impl Tag { + + pub fn new_compound() -> Tag { + Tag::Compound(HashMap::new()) + } + + pub fn new_list() -> Tag { + Tag::List(Vec::new()) + } + + /// Returns the tag with the given name from the compound. + /// + /// # Panics + /// Panics when the tag isn't a compound. + pub fn get(&self, name: &str) -> Option<&Tag> { + match *self { + Tag::Compound(ref val) => val.get(name), + _ => panic!("not a compound tag"), + } + } + + /// Places the tag into the compound using the given name. + /// + /// # Panics + /// Panics when the tag isn't a compound. + pub fn put(&mut self, name: &str, tag: Tag) { + match *self { + Tag::Compound(ref mut val) => val.insert(name.to_owned(), tag), + _ => panic!("not a compound tag"), + }; + } + + pub fn is_compound(&self) -> bool { + match *self { + Tag::Compound(_) => true, + _ => false, + } + } + + pub fn as_byte(&self) -> Option { + match *self { + Tag::Byte(val) => Some(val), + _ => None, + } + } + + pub fn as_short(&self) -> Option { + match *self { + Tag::Short(val) => Some(val), + _ => None, + } + } + + pub fn as_int(&self) -> Option { + match *self { + Tag::Int(val) => Some(val), + _ => None, + } + } + + pub fn as_long(&self) -> Option { + match *self { + Tag::Long(val) => Some(val), + _ => None, + } + } + + pub fn as_float(&self) -> Option { + match *self { + Tag::Float(val) => Some(val), + _ => None, + } + } + + pub fn as_double(&self) -> Option { + match *self { + Tag::Double(val) => Some(val), + _ => None, + } + } + + pub fn as_byte_array(&self) -> Option<&[u8]> { + match *self { + Tag::ByteArray(ref val) => Some(&val[..]), + _ => None, + } + } + + pub fn as_str(&self) -> Option<&str> { + match *self { + Tag::String(ref val) => Some(&val[..]), + _ => None, + } + } + + pub fn as_list(&self) -> Option<&[Tag]> { + match *self { + Tag::List(ref val) => Some(&val[..]), + _ => None, + } + } + + pub fn as_compound(&self) -> Option<&HashMap> { + match *self { + Tag::Compound(ref val) => Some(val), + _ => None, + } + } + + pub fn as_int_array(&self) -> Option<&[i32]> { + match *self { + Tag::IntArray(ref val) => Some(&val[..]), + _ => None, + } + } + + pub fn as_long_array(&self) -> Option<&[i64]> { + match *self { + Tag::LongArray(ref val) => Some(&val[..]), + _ => None, + } + } + + + fn internal_id(&self) -> u8 { + match *self { + Tag::End => 0, + Tag::Byte(_) => 1, + Tag::Short(_) => 2, + Tag::Int(_) => 3, + Tag::Long(_) => 4, + Tag::Float(_) => 5, + Tag::Double(_) => 6, + Tag::ByteArray(_) => 7, + Tag::String(_) => 8, + Tag::List(_) => 9, + Tag::Compound(_) => 10, + Tag::IntArray(_) => 11, + Tag::LongArray(_) => 12, + } + } + + fn read_type(id: u8, buf: &mut R) -> Result { + match id { + 0 => unreachable!(), + 1 => Ok(Tag::Byte(buf.read_i8()?)), + 2 => Ok(Tag::Short(buf.read_i16::()?)), + 3 => Ok(Tag::Int(buf.read_i32::()?)), + 4 => Ok(Tag::Long(buf.read_i64::()?)), + 5 => Ok(Tag::Float(buf.read_f32::()?)), + 6 => Ok(Tag::Double(buf.read_f64::()?)), + 7 => Ok(Tag::ByteArray({ + let len: i32 = Serializable::read_from(buf)?; + let mut data = Vec::with_capacity(len as usize); + buf.take(len as u64).read_to_end(&mut data)?; + data + })), + 8 => Ok(Tag::String(read_string(buf)?)), + 9 => { + let mut l = Vec::new(); + let ty = buf.read_u8()?; + let len: i32 = Serializable::read_from(buf)?; + for _ in 0..len { + l.push(Tag::read_type(ty, buf)?); + } + Ok(Tag::List(l)) + } + 10 => { + let mut c = Tag::new_compound(); + loop { + let ty = buf.read_u8()?; + if ty == 0 { + break; + } + let name: String = read_string(buf)?; + c.put(&name[..], Tag::read_type(ty, buf)?); + } + Ok(c) + } + 11 => Ok(Tag::IntArray({ + let len: i32 = Serializable::read_from(buf)?; + let mut data = Vec::with_capacity(len as usize); + for _ in 0..len { + data.push(buf.read_i32::()?); + } + data + })), + 12 => Ok(Tag::LongArray({ + let len: i32 = Serializable::read_from(buf)?; + let mut data = Vec::with_capacity(len as usize); + for _ in 0..len { + data.push(buf.read_i64::()?); + } + data + })), + _ => Err(protocol::Error::Err("invalid tag".to_owned())), + } + } +} + +impl Serializable for Tag { + fn read_from(buf: &mut R) -> Result { + Tag::read_type(10, buf) + } + + fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { + match *self { + Tag::End => {} + Tag::Byte(val) => buf.write_i8(val)?, + Tag::Short(val) => buf.write_i16::(val)?, + Tag::Int(val) => buf.write_i32::(val)?, + Tag::Long(val) => buf.write_i64::(val)?, + Tag::Float(val) => buf.write_f32::(val)?, + Tag::Double(val) => buf.write_f64::(val)?, + Tag::ByteArray(ref val) => { + (val.len() as i32).write_to(buf)?; + buf.write_all(val)?; + } + Tag::String(ref val) => write_string(buf, val)?, + Tag::List(ref val) => { + if val.is_empty() { + buf.write_u8(0)?; + buf.write_i32::(0)?; + } else { + buf.write_u8(val[0].internal_id())?; + buf.write_i32::(val.len() as i32)?; + for e in val { + e.write_to(buf)?; + } + } + } + Tag::Compound(ref val) => { + for (k, v) in val { + v.internal_id().write_to(buf)?; + write_string(buf, k)?; + v.write_to(buf)?; + } + buf.write_u8(0)?; + } + Tag::IntArray(ref val) => { + (val.len() as i32).write_to(buf)?; + for v in val { + v.write_to(buf)?; + } + } + Tag::LongArray(ref val) => { + (val.len() as i32).write_to(buf)?; + for v in val { + v.write_to(buf)?; + } + } + } + Result::Ok(()) + } +} + +pub fn write_string(buf: &mut W, s: &str) -> Result<(), protocol::Error> { + let data = s.as_bytes(); + (data.len() as i16).write_to(buf)?; + buf.write_all(data).map_err(|v| v.into()) +} + +pub fn read_string(buf: &mut R) -> Result { + let len: i16 = buf.read_i16::()?; + let mut bytes = Vec::::new(); + buf.take(len as u64).read_to_end(&mut bytes)?; + let ret = String::from_utf8(bytes).unwrap(); + Result::Ok(ret) +} diff --git a/protocol/src/protocol/forge.rs b/protocol/src/protocol/forge.rs new file mode 100644 index 0000000..c3096e1 --- /dev/null +++ b/protocol/src/protocol/forge.rs @@ -0,0 +1,194 @@ + +/// Implements https://wiki.vg/Minecraft_Forge_Handshake +use std::io; +use byteorder::WriteBytesExt; +use log::debug; + +use super::{Serializable, Error, LenPrefixed, VarInt}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Phase { + // Client handshake states (written) + Start, + WaitingServerData, + WaitingServerComplete, + PendingComplete, + + // Server handshake states (read) + WaitingCAck, + + // Both client and server handshake states (different values on the wire) + Complete, +} + +impl Serializable for Phase { + /// Read server handshake state from server + fn read_from(buf: &mut R) -> Result { + let phase: i8 = Serializable::read_from(buf)?; + Ok(match phase { + 2 => Phase::WaitingCAck, + 3 => Phase::Complete, + _ => panic!("bad FML|HS server phase: {}", phase), + }) + } + + /// Send client handshake state from client + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u8(match self { + Phase::WaitingServerData => 2, + Phase::WaitingServerComplete => 3, + Phase::PendingComplete => 4, + Phase::Complete => 5, + _ => panic!("bad FML|HS client phase: {:?}", self), + })?; + Ok(()) + } +} + + +#[derive(Clone, Debug, Default)] +pub struct ForgeMod { + pub modid: String, + pub version: String, +} + +impl Serializable for ForgeMod { + fn read_from(buf: &mut R) -> Result { + Ok(ForgeMod { + modid: Serializable::read_from(buf)?, + version: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.modid.write_to(buf)?; + self.version.write_to(buf) + } +} + +#[derive(Debug)] +pub struct ModIdMapping { + pub name: String, + pub id: VarInt, +} + +impl Serializable for ModIdMapping { + fn read_from(buf: &mut R) -> Result { + Ok(ModIdMapping { + name: Serializable::read_from(buf)?, + id: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.name.write_to(buf)?; + self.id.write_to(buf) + } +} + +pub static BLOCK_NAMESPACE: &'static str = "\u{1}"; +pub static ITEM_NAMESPACE: &'static str = "\u{2}"; + +#[derive(Debug)] +pub enum FmlHs { + ServerHello { + fml_protocol_version: i8, + override_dimension: Option, + }, + ClientHello { + fml_protocol_version: i8, + }, + ModList { + mods: LenPrefixed, + }, + RegistryData { + has_more: bool, + name: String, + ids: LenPrefixed, + substitutions: LenPrefixed, + dummies: LenPrefixed, + }, + ModIdData { + mappings: LenPrefixed, + block_substitutions: LenPrefixed, + item_substitutions: LenPrefixed, + }, + HandshakeAck { + phase: Phase, + }, + HandshakeReset, +} + +impl Serializable for FmlHs { + fn read_from(buf: &mut R) -> Result { + let discriminator: u8 = Serializable::read_from(buf)?; + + match discriminator { + 0 => { + let fml_protocol_version: i8 = Serializable::read_from(buf)?; + let override_dimension = if fml_protocol_version > 1 { + let dimension: i32 = Serializable::read_from(buf)?; + Some(dimension) + } else { + None + }; + + debug!("FML|HS ServerHello: fml_protocol_version={}, override_dimension={:?}", fml_protocol_version, override_dimension); + + Ok(FmlHs::ServerHello { + fml_protocol_version, + override_dimension, + }) + }, + 1 => panic!("Received unexpected FML|HS ClientHello from server"), + 2 => { + Ok(FmlHs::ModList { + mods: Serializable::read_from(buf)?, + }) + }, + 3 => { + let protocol_version = super::current_protocol_version(); + + if protocol_version >= 47 { + Ok(FmlHs::RegistryData { + has_more: Serializable::read_from(buf)?, + name: Serializable::read_from(buf)?, + ids: Serializable::read_from(buf)?, + substitutions: Serializable::read_from(buf)?, + dummies: Serializable::read_from(buf)?, + }) + } else { + Ok(FmlHs::ModIdData { + mappings: Serializable::read_from(buf)?, + block_substitutions: Serializable::read_from(buf)?, + item_substitutions: Serializable::read_from(buf)?, + }) + } + }, + 255 => { + Ok(FmlHs::HandshakeAck { + phase: Serializable::read_from(buf)?, + }) + }, + _ => panic!("Unhandled FML|HS packet: discriminator={}", discriminator), + } + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + match self { + FmlHs::ClientHello { fml_protocol_version } => { + buf.write_u8(1)?; + fml_protocol_version.write_to(buf) + }, + FmlHs::ModList { mods } => { + buf.write_u8(2)?; + mods.write_to(buf) + }, + FmlHs::HandshakeAck { phase } => { + buf.write_u8(255)?; + phase.write_to(buf) + }, + _ => unimplemented!() + } + } +} diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs new file mode 100644 index 0000000..13be61a --- /dev/null +++ b/protocol/src/protocol/mod.rs @@ -0,0 +1,1319 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use aes::Aes128; +use cfb8::Cfb8; +use cfb8::stream_cipher::{NewStreamCipher, StreamCipher}; +use serde_json; +use std_or_web::fs; +#[cfg(not(target_arch = "wasm32"))] +use reqwest; +use hex; + +pub mod mojang; +pub mod forge; + +use crate::nbt; +use crate::format; +use std::fmt; +use std::default; +use std::net::TcpStream; +use std::io; +use std::io::{Write, Read}; +use std::convert; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use flate2::read::{ZlibDecoder, ZlibEncoder}; +use flate2::Compression; +use std::time::{Instant, Duration}; +use crate::shared::Position; +use log::debug; + +pub const SUPPORTED_PROTOCOLS: [i32; 18] = [575, 498, 490, 485, 480, 477, 452, 451, 404, 340, 316, 315, 210, 109, 107, 74, 47, 5]; + +static CURRENT_PROTOCOL_VERSION: AtomicI32 = AtomicI32::new(SUPPORTED_PROTOCOLS[0]); +static NETWORK_DEBUG: AtomicBool = AtomicBool::new(false); + +pub fn current_protocol_version() -> i32 { + CURRENT_PROTOCOL_VERSION.load(Ordering::Relaxed) +} + +pub fn enable_network_debug() { + NETWORK_DEBUG.store(true, Ordering::Relaxed); +} + +pub fn is_network_debug() -> bool { + NETWORK_DEBUG.load(Ordering::Relaxed) +} + +/// Helper macro for defining packets +#[macro_export] +macro_rules! state_packets { + ($($state:ident $stateName:ident { + $($dir:ident $dirName:ident { + $( + $(#[$attr:meta])* + packet $name:ident { + $($(#[$fattr:meta])*field $field:ident: $field_type:ty = $(when ($cond:expr))*, )+ + } + )* + })+ + })+) => { + use crate::protocol::*; + use std::io; + + #[derive(Debug)] + pub enum Packet { + $( + $( + $( + $name($state::$dir::$name), + )* + )+ + )+ + } + + $( + pub mod $state { + + $( + pub mod $dir { + #![allow(unused_imports)] + use crate::protocol::*; + use std::io; + use crate::format; + use crate::nbt; + use crate::types; + use crate::item; + use crate::shared::Position; + + + #[allow(non_upper_case_globals)] + pub mod internal_ids { + create_ids!(i32, $($name),*); + } + + $( + #[derive(Default, Debug)] + $(#[$attr])* pub struct $name { + $($(#[$fattr])* pub $field: $field_type),+, + } + + impl PacketType for $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> { + $( + if true $(&& ($cond(&self)))* { + self.$field.write_to(buf)?; + } + )+ + + Result::Ok(()) + } + } + )* + } + )+ + } + )+ + + /// Returns the packet for the given state, direction and id after parsing the fields + /// from the buffer. + 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 => { + 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; + let mut packet : $name = $name::default(); + $( + if true $(&& ($cond(&packet)))* { + packet.$field = Serializable::read_from(&mut buf)?; + } + )+ + Result::Ok(Option::Some(Packet::$name(packet))) + }, + )* + _ => Result::Ok(Option::None) + } + } + )+ + } + } + )+ + } + } + } +} + +#[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 0x{:x} in {:?} {:?}", id, dir, state), + } + } else { + match id { + $( + crate::protocol::packet::$state::$dir::internal_ids::$name => $id, + )* + _ => panic!("bad packet internal id 0x{:x} in {:?} {:?}", id, 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>; +} + +impl Serializable for Vec { + fn read_from(buf: &mut R) -> Result, Error> { + let mut v = Vec::new(); + buf.read_to_end(&mut v)?; + Ok(v) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_all(&self[..]).map_err(|v| v.into()) + } +} + +impl Serializable for Option{ + fn read_from(buf: &mut R) -> Result, Error> { + let ty = buf.read_u8()?; + if ty == 0 { + Result::Ok(None) + } else { + let name = nbt::read_string(buf)?; + let tag = nbt::Tag::read_from(buf)?; + Result::Ok(Some(nbt::NamedTag(name, tag))) + } + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + match *self { + Some(ref val) => { + buf.write_u8(10)?; + nbt::write_string(buf, &val.0)?; + val.1.write_to(buf)?; + } + None => buf.write_u8(0)?, + } + Result::Ok(()) + } +} + +impl Serializable for Option where T : Serializable { + fn read_from(buf: &mut R) -> Result, Error> { + Result::Ok(Some(T::read_from(buf)?)) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + if self.is_some() { + self.as_ref().unwrap().write_to(buf)?; + } + Result::Ok(()) + } +} + +impl Serializable for String { + fn read_from(buf: &mut R) -> Result { + let len = VarInt::read_from(buf)?.0; + debug_assert!(len >= 0, "Negative string length: {}", len); + debug_assert!(len <= 65536, "String length too big: {}", len); + let mut bytes = Vec::::new(); + buf.take(len as u64).read_to_end(&mut bytes)?; + let ret = String::from_utf8(bytes).unwrap(); + Result::Ok(ret) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + let bytes = self.as_bytes(); + VarInt(bytes.len() as i32).write_to(buf)?; + buf.write_all(bytes)?; + Result::Ok(()) + } +} + +impl Serializable for format::Component { + fn read_from(buf: &mut R) -> Result { + let len = VarInt::read_from(buf)?.0; + let mut bytes = Vec::::new(); + buf.take(len as u64).read_to_end(&mut bytes)?; + let ret = String::from_utf8(bytes).unwrap(); + Result::Ok(Self::from_string(&ret[..])) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + let val = serde_json::to_string(&self.to_value()).unwrap(); + let bytes = val.as_bytes(); + VarInt(bytes.len() as i32).write_to(buf)?; + buf.write_all(bytes)?; + Result::Ok(()) + } +} + +impl Serializable for () { + fn read_from(_: &mut R) -> Result<(), Error> { + Result::Ok(()) + } + fn write_to(&self, _: &mut W) -> Result<(), Error> { + Result::Ok(()) + } +} + +impl Serializable for bool { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_u8()? != 0) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u8(if *self { + 1 + } else { + 0 + })?; + Result::Ok(()) + } +} + +impl Serializable for i8 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_i8()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_i8(*self)?; + Result::Ok(()) + } +} + +impl Serializable for i16 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_i16::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_i16::(*self)?; + Result::Ok(()) + } +} + +impl Serializable for i32 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_i32::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_i32::(*self)?; + Result::Ok(()) + } +} + +impl Serializable for i64 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_i64::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_i64::(*self)?; + Result::Ok(()) + } +} + +impl Serializable for u8 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_u8()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u8(*self)?; + Result::Ok(()) + } +} + +impl Serializable for u16 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_u16::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u16::(*self)?; + Result::Ok(()) + } +} + +impl Serializable for u64 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_u64::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u64::(*self)?; + Result::Ok(()) + } +} + +impl Serializable for f32 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_f32::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_f32::(*self)?; + Result::Ok(()) + } +} + +impl Serializable for f64 { + fn read_from(buf: &mut R) -> Result { + Result::Ok(buf.read_f64::()?) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_f64::(*self)?; + Result::Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct UUID(u64, u64); + +impl UUID { + pub fn from_str(s: &str) -> UUID { + // TODO: Panics aren't the best idea here + if s.len() != 36 { + panic!("Invalid UUID format"); + } + let mut parts = hex::decode(&s[..8]).unwrap(); + parts.extend_from_slice(&hex::decode(&s[9..13]).unwrap()); + parts.extend_from_slice(&hex::decode(&s[14..18]).unwrap()); + parts.extend_from_slice(&hex::decode(&s[19..23]).unwrap()); + parts.extend_from_slice(&hex::decode(&s[24..36]).unwrap()); + let mut high = 0u64; + let mut low = 0u64; + for i in 0 .. 8 { + high |= (parts[i] as u64) << (56 - i*8); + low |= (parts[i + 8] as u64) << (56 - i*8); + } + UUID(high, low) + } +} + +impl Default for UUID { + fn default() -> Self { + UUID(0, 0) + } +} + +impl Serializable for UUID { + fn read_from(buf: &mut R) -> Result { + Result::Ok(UUID(buf.read_u64::()?, + buf.read_u64::()?)) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + buf.write_u64::(self.0)?; + buf.write_u64::(self.1)?; + Result::Ok(()) + } +} + +pub struct Biomes3D { + pub data: [i32; 1024], +} + +impl fmt::Debug for Biomes3D { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Biomes3D(")?; + for i in 0..1024 { + write!(f, "{}, ", self.data[i])?; + } + write!(f, ")") + } +} + +impl Default for Biomes3D { + fn default() -> Self { + Biomes3D { data: [0; 1024] } + } +} + +impl Serializable for Biomes3D { + fn read_from(buf: &mut R) -> Result { + let mut data: [i32; 1024] = [0; 1024]; + + // Non-length-prefixed three-dimensional biome data + for i in 0..1024 { + let b: i32 = Serializable::read_from(buf)?; + data[i] = b; + } + + Result::Ok(Biomes3D { data }) + } + fn write_to(&self, _buf: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + + +pub trait Lengthable : Serializable + Copy + Default { + fn into_len(self) -> usize; + fn from_len(_: usize) -> Self; +} + +pub struct LenPrefixed { + len: L, + pub data: Vec, +} + +impl LenPrefixed { + pub fn new(data: Vec) -> LenPrefixed { + LenPrefixed { + len: Default::default(), + data, + } + } +} + +impl Serializable for LenPrefixed { + fn read_from(buf: &mut R) -> Result, Error> { + let len_data: L = Serializable::read_from(buf)?; + let len: usize = len_data.into_len(); + let mut data: Vec = Vec::with_capacity(len); + for _ in 0..len { + data.push(Serializable::read_from(buf)?); + } + Result::Ok(LenPrefixed { + len: len_data, + data, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + let len_data: L = L::from_len(self.data.len()); + len_data.write_to(buf)?; + let data = &self.data; + for val in data { + val.write_to(buf)?; + } + Result::Ok(()) + } +} + + +impl Default for LenPrefixed { + fn default() -> Self { + LenPrefixed { + len: default::Default::default(), + data: default::Default::default(), + } + } +} + +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 { + pub fn new(data: Vec) -> LenPrefixedBytes { + LenPrefixedBytes { + len: Default::default(), + data, + } + } +} + +impl Serializable for LenPrefixedBytes { + fn read_from(buf: &mut R) -> Result, Error> { + let len_data: L = Serializable::read_from(buf)?; + let len: usize = len_data.into_len(); + let mut data: Vec = Vec::with_capacity(len); + buf.take(len as u64).read_to_end(&mut data)?; + Result::Ok(LenPrefixedBytes { + len: len_data, + data, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + let len_data: L = L::from_len(self.data.len()); + len_data.write_to(buf)?; + 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 bool { + fn into_len(self) -> usize { + if self { 1 } else { 0 } + } + + fn from_len(u: usize) -> bool { + u != 0 + } +} + + +impl Lengthable for u8 { + fn into_len(self) -> usize { + self as usize + } + + fn from_len(u: usize) -> u8 { + u as u8 + } +} + +impl Lengthable for i16 { + fn into_len(self) -> usize { + self as usize + } + + fn from_len(u: usize) -> i16 { + u as i16 + } +} + +impl Lengthable for i32 { + fn into_len(self) -> usize { + self as usize + } + + fn from_len(u: usize) -> i32 { + u as i32 + } +} + +use num_traits::cast::{cast, NumCast}; +/// `FixedPoint5` has the 5 least-significant bits for the fractional +/// part, upper for integer part: https://wiki.vg/Data_types#Fixed-point_numbers +#[derive(Clone, Copy)] +pub struct FixedPoint5(T); + +impl Serializable for FixedPoint5 { + fn read_from(buf: &mut R) -> Result { + Ok(Self(Serializable::read_from(buf)?)) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.0.write_to(buf) + } +} + +impl default::Default for FixedPoint5 { + fn default() -> Self { + Self(T::default()) + } +} + +impl convert::From for FixedPoint5 { + fn from(x: f64) -> Self { + let n: T = cast(x * 32.0).unwrap(); + FixedPoint5::(n) + } +} + +impl convert::From> for f64 { + fn from(x: FixedPoint5) -> Self { + let f: f64 = cast(x.0).unwrap(); + f / 32.0 + } +} + +impl fmt::Debug for FixedPoint5 where T: fmt::Display, f64: convert::From, T: NumCast + Copy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let x: f64 = (*self).into(); + write!(f, "FixedPoint5(#{} = {}f)", self.0, x) + } +} + +/// `FixedPoint12` is like `FixedPoint5` but the fractional part is 12-bit +#[derive(Clone, Copy)] +pub struct FixedPoint12(T); + +impl Serializable for FixedPoint12 { + fn read_from(buf: &mut R) -> Result { + Ok(Self(Serializable::read_from(buf)?)) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.0.write_to(buf) + } +} + +impl default::Default for FixedPoint12 { + fn default() -> Self { + Self(T::default()) + } +} + +impl convert::From for FixedPoint12 { + fn from(x: f64) -> Self { + let n: T = cast(x * 32.0 * 128.0).unwrap(); + FixedPoint12::(n) + } +} + +impl convert::From> for f64 { + fn from(x: FixedPoint12) -> Self { + let f: f64 = cast(x.0).unwrap(); + f / (32.0 * 128.0) + } +} + +impl fmt::Debug for FixedPoint12 where T: fmt::Display, f64: convert::From, T: NumCast + Copy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let x: f64 = (*self).into(); + write!(f, "FixedPoint12(#{} = {}f)", self.0, x) + } +} + +/// `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(pub i32); + +impl Lengthable for VarInt { + fn into_len(self) -> usize { + self.0 as usize + } + + fn from_len(u: usize) -> VarInt { + VarInt(u as i32) + } +} + +impl Serializable for VarInt { + /// Decodes a `VarInt` from the Reader + fn read_from(buf: &mut R) -> Result { + const PART : u32 = 0x7F; + let mut size = 0; + let mut val = 0u32; + loop { + let b = buf.read_u8()? as u32; + val |= (b & PART) << (size * 7); + size += 1; + if size > 5 { + return Result::Err(Error::Err("VarInt too big".to_owned())); + } + if (b & 0x80) == 0 { + break + } + } + + Result::Ok(VarInt(val as i32)) + } + + /// Encodes a `VarInt` into the Writer + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + const PART : u32 = 0x7F; + let mut val = self.0 as u32; + loop { + if (val & !PART) == 0 { + buf.write_u8(val as u8)?; + return Result::Ok(()); + } + buf.write_u8(((val & PART) | 0x80) as u8)?; + val >>= 7; + } + } +} + +impl default::Default for VarInt { + fn default() -> VarInt { + VarInt(0) + } +} + +impl fmt::Debug for VarInt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// `VarShort` have a variable size (2 or 3 bytes) and are backwards-compatible +/// with vanilla shorts, used for Forge custom payloads +#[derive(Clone, Copy)] +pub struct VarShort(pub i32); + +impl Lengthable for VarShort { + fn into_len(self) -> usize { + self.0 as usize + } + + fn from_len(u: usize) -> VarShort { + VarShort(u as i32) + } +} + +impl Serializable for VarShort { + fn read_from(buf: &mut R) -> Result { + let low = buf.read_u16::()? as u32; + let val = if (low & 0x8000) != 0 { + let high = buf.read_u8()? as u32; + + (high << 15) | (low & 0x7fff) + } else { + low + }; + + Result::Ok(VarShort(val as i32)) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + assert!(self.0 >= 0 && self.0 <= 0x7fffff, "VarShort invalid value: {}", self.0); + let mut low = self.0 & 0x7fff; + let high = (self.0 & 0x7f8000) >> 15; + if high != 0 { + low |= 0x8000; + } + + buf.write_u16::(low as u16)?; + + if high != 0 { + buf.write_u8(high as u8)?; + } + + Ok(()) + } +} + +impl default::Default for VarShort { + fn default() -> VarShort { + VarShort(0) + } +} + +impl fmt::Debug for VarShort { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// `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_len(self) -> usize { + self.0 as usize + } + + fn from_len(u: usize) -> VarLong { + VarLong(u as i64) + } +} + +impl Serializable for VarLong { + /// Decodes a `VarLong` from the Reader + fn read_from(buf: &mut R) -> Result { + const PART : u64 = 0x7F; + let mut size = 0; + let mut val = 0u64; + loop { + let b = buf.read_u8()? as u64; + val |= (b & PART) << (size * 7); + size += 1; + if size > 10 { + return Result::Err(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 W) -> Result<(), Error> { + const PART : u64 = 0x7F; + let mut val = self.0 as u64; + loop { + if (val & !PART) == 0 { + buf.write_u8(val as u8)?; + return Result::Ok(()); + } + 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) + } +} + +impl Serializable for Position { + fn read_from(buf: &mut R) -> Result { + let pos = buf.read_u64::()?; + Ok(Position::new( + ((pos as i64) >> 38) as i32, + (((pos as i64) >> 26) & 0xFFF) as i32, + ((pos as i64) << 38 >> 38) as i32 + )) + } + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + let pos = (((self.x as u64) & 0x3FFFFFF) << 38) + | (((self.y as u64) & 0xFFF) << 26) + | ((self.z as u64) & 0x3FFFFFF); + buf.write_u64::(pos)?; + Result::Ok(()) + } +} + + +/// Direction is used to define whether packets are going to the +/// server or the client. +#[derive(Clone, Copy, Debug)] +pub enum Direction { + Serverbound, + Clientbound, +} + +/// The protocol has multiple 'sub-protocols' or states which control which +/// packet an id points to. +#[derive(Clone, Copy, Debug)] +pub enum State { + Handshaking, + Play, + Status, + Login, +} + +/// Return for any protocol related error. +#[derive(Debug)] +pub enum Error { + Err(String), + Disconnect(format::Component), + IOError(io::Error), + Json(serde_json::Error), + #[cfg(not(target_arch = "wasm32"))] + Reqwest(reqwest::Error), +} + +impl convert::From for Error { + fn from(e: io::Error) -> Error { + Error::IOError(e) + } +} + +impl convert::From for Error { + fn from(e: serde_json::Error) -> Error { + Error::Json(e) + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl convert::From for Error { + fn from(e: reqwest::Error) -> Error { + Error::Reqwest(e) + } +} + +impl ::std::error::Error for Error {} + +impl ::std::fmt::Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + Error::Err(ref val) => write!(f, "protocol error: {}", val), + Error::Disconnect(ref val) => write!(f, "{}", val), + Error::IOError(ref e) => e.fmt(f), + Error::Json(ref e) => e.fmt(f), + #[cfg(not(target_arch = "wasm32"))] + Error::Reqwest(ref e) => e.fmt(f), + } + } +} + +type Aes128Cfb = Cfb8; + +pub struct Conn { + stream: TcpStream, + pub host: String, + pub port: u16, + direction: Direction, + pub protocol_version: i32, + pub state: State, + + cipher: Option, + + compression_threshold: i32, +} + +impl Conn { + pub fn new(target: &str, protocol_version: i32) -> Result { + CURRENT_PROTOCOL_VERSION.store(protocol_version, Ordering::Relaxed); + + // TODO SRV record support + let mut parts = target.split(':').collect::>(); + let address = if parts.len() == 1 { + parts.push("25565"); + format!("{}:25565", parts[0]) + } else { + format!("{}:{}", parts[0], parts[1]) + }; + let stream = TcpStream::connect(&*address)?; + Result::Ok(Conn { + stream, + host: parts[0].to_owned(), + port: parts[1].parse().unwrap(), + direction: Direction::Serverbound, + state: State::Handshaking, + protocol_version, + cipher: Option::None, + compression_threshold: -1, + }) + } + + pub fn write_packet(&mut self, packet: T) -> Result<(), Error> { + let mut buf = Vec::new(); + VarInt(packet.packet_id(self.protocol_version)).write_to(&mut buf)?; + packet.write(&mut buf)?; + + let mut extra = if self.compression_threshold >= 0 { + 1 + } else { + 0 + }; + if self.compression_threshold >= 0 && buf.len() as i32 > self.compression_threshold { + extra = 0; + let uncompressed_size = buf.len(); + let mut new = Vec::new(); + VarInt(uncompressed_size as i32).write_to(&mut new)?; + let mut write = ZlibEncoder::new(io::Cursor::new(buf), Compression::default()); + write.read_to_end(&mut new)?; + if is_network_debug() { + debug!("Compressed for sending {} bytes to {} since > threshold {}, new={:?}", + uncompressed_size, new.len(), self.compression_threshold, + new); + } + buf = new; + } + + VarInt(buf.len() as i32 + extra).write_to(self)?; + if self.compression_threshold >= 0 && extra == 1 { + VarInt(0).write_to(self)?; + } + self.write_all(&buf)?; + + Result::Ok(()) + } + + pub fn read_packet(&mut self) -> Result { + let len = VarInt::read_from(self)?.0 as usize; + let mut ibuf = vec![0; len]; + self.read_exact(&mut ibuf)?; + + let mut buf = io::Cursor::new(ibuf); + + if self.compression_threshold >= 0 { + let uncompressed_size = VarInt::read_from(&mut buf)?.0; + if uncompressed_size != 0 { + let mut new = Vec::with_capacity(uncompressed_size as usize); + { + let mut reader = ZlibDecoder::new(buf); + reader.read_to_end(&mut new)?; + } + if is_network_debug() { + debug!("Decompressed threshold={} len={} uncompressed_size={} to {} bytes", + self.compression_threshold, len, uncompressed_size, new.len()); + } + buf = io::Cursor::new(new); + } + } + let id = VarInt::read_from(&mut buf)?.0; + + let dir = match self.direction { + Direction::Clientbound => Direction::Serverbound, + Direction::Serverbound => Direction::Clientbound, + }; + + if is_network_debug() { + debug!("about to parse id={:x}, dir={:?} state={:?}", id, dir, self.state); + fs::File::create("last-packet")?.write_all(buf.get_ref())?; + } + + let packet = packet::packet_by_id(self.protocol_version, self.state, dir, id, &mut buf)?; + + if is_network_debug() { + debug!("packet = {:?}", packet); + } + + match packet { + Some(val) => { + let pos = buf.position() as usize; + let ibuf = buf.into_inner(); + 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) + } + None => Result::Err(Error::Err("missing packet".to_owned())), + } + } + + pub fn enable_encyption(&mut self, key: &[u8], _decrypt: bool) { + let cipher = Aes128Cfb::new_var(key, key).unwrap(); + self.cipher = Option::Some(cipher); + } + + pub fn set_compresssion(&mut self, threshold: i32) { + self.compression_threshold = threshold; + } + + pub fn do_status(mut self) -> Result<(Status, Duration), Error> { + use serde_json::Value; + use self::packet::status::serverbound::*; + use self::packet::handshake::serverbound::Handshake; + use self::packet::Packet; + let host = self.host.clone(); + let port = self.port; + self.write_packet(Handshake { + protocol_version: VarInt(self.protocol_version), + host, + port, + next: VarInt(1), + })?; + self.state = State::Status; + + self.write_packet(StatusRequest { empty: () })?; + + let status = if let Packet::StatusResponse(res) = self.read_packet()? { + res.status + } else { + return Err(Error::Err("Wrong packet".to_owned())); + }; + + let start = Instant::now(); + self.write_packet(StatusPing { ping: 42 })?; + + if let Packet::StatusPong(_) = self.read_packet()? { + } else { + return Err(Error::Err("Wrong packet".to_owned())); + }; + + let ping = start.elapsed(); + + let val: Value = match serde_json::from_str(&status) { + Ok(val) => val, + Err(_) => return Err(Error::Err("Json parse error".to_owned())), + }; + + let invalid_status = || Error::Err("Invalid status".to_owned()); + + let version = val.get("version").ok_or(invalid_status())?; + let players = val.get("players").ok_or(invalid_status())?; + + // For modded servers, get the list of Forge mods installed + let mut forge_mods: std::vec::Vec = vec![]; + if let Some(modinfo) = val.get("modinfo") { + if let Some(modinfo_type) = modinfo.get("type") { + if modinfo_type == "FML" { + if let Some(modlist) = modinfo.get("modList") { + if let Value::Array(items) = modlist { + for item in items { + if let Value::Object(obj) = item { + let modid = obj.get("modid").unwrap().as_str().unwrap().to_string(); + let version = obj.get("version").unwrap().as_str().unwrap().to_string(); + + forge_mods.push(crate::protocol::forge::ForgeMod { modid, version }); + } + } + } + } + } else { + panic!("Unrecognized modinfo type in server ping response: {} in {}", modinfo_type, modinfo); + } + } + } + + Ok((Status { + version: StatusVersion { + name: version.get("name").and_then(Value::as_str).ok_or(invalid_status())? + .to_owned(), + protocol: version.get("protocol") + .and_then(Value::as_i64) + .ok_or(invalid_status())? as i32, + }, + players: StatusPlayers { + max: players.get("max") + .and_then(Value::as_i64) + .ok_or(invalid_status())? as i32, + online: players.get("online") + .and_then(Value::as_i64) + .ok_or(invalid_status())? as i32, + sample: Vec::new(), /* TODO */ + }, + description: format::Component::from_value(val.get("description") + .ok_or(invalid_status())?), + favicon: val.get("favicon").and_then(Value::as_str).map(|v| v.to_owned()), + forge_mods, + }, + ping)) + } +} + +#[derive(Debug)] +pub struct Status { + pub version: StatusVersion, + pub players: StatusPlayers, + pub description: format::Component, + pub favicon: Option, + pub forge_mods: Vec, +} + +#[derive(Debug)] +pub struct StatusVersion { + pub name: String, + pub protocol: i32, +} + +#[derive(Debug)] +pub struct StatusPlayers { + pub max: i32, + pub online: i32, + pub sample: Vec, +} + +#[derive(Debug)] +pub struct StatusPlayer { + name: String, + id: String, +} + +impl Read for Conn { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.cipher.as_mut() { + Option::None => self.stream.read(buf), + Option::Some(cipher) => { + let ret = self.stream.read(buf)?; + cipher.decrypt(&mut buf[..ret]); + + Ok(ret) + } + } + } +} + +impl Write for Conn { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.cipher.as_mut() { + Option::None => self.stream.write(buf), + Option::Some(cipher) => { + // TODO: avoid copying, but trait requires non-mutable buf + let mut data = vec![0; buf.len()]; + for i in 0..buf.len() { + data[i] = buf[i]; + } + + cipher.encrypt(&mut data); + + self.stream.write_all(&data)?; + Ok(buf.len()) + } + } + } + + fn flush(&mut self) -> io::Result<()> { + self.stream.flush() + } +} + +impl Clone for Conn { + fn clone(&self) -> Self { + Conn { + stream: self.stream.try_clone().unwrap(), + host: self.host.clone(), + port: self.port, + direction: self.direction, + state: self.state, + protocol_version: self.protocol_version, + cipher: Option::None, + compression_threshold: self.compression_threshold, + } + } +} + +pub trait PacketType { + fn packet_id(&self, protocol_version: i32) -> i32; + + fn write(self, buf: &mut W) -> Result<(), Error>; +} diff --git a/protocol/src/protocol/mojang.rs b/protocol/src/protocol/mojang.rs new file mode 100644 index 0000000..165fa38 --- /dev/null +++ b/protocol/src/protocol/mojang.rs @@ -0,0 +1,159 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sha1::{self, Digest}; +use serde_json::json; +#[cfg(not(target_arch = "wasm32"))] +use reqwest; + +#[derive(Clone, Debug)] +pub struct Profile { + pub username: String, + pub id: String, + pub access_token: String, +} + +const JOIN_URL: &str = "https://sessionserver.mojang.com/session/minecraft/join"; +const LOGIN_URL: &str = "https://authserver.mojang.com/authenticate"; +const REFRESH_URL: &str = "https://authserver.mojang.com/refresh"; +const VALIDATE_URL: &str = "https://authserver.mojang.com/validate"; + +#[cfg(not(target_arch = "wasm32"))] +impl Profile { + pub fn login(username: &str, password: &str, token: &str) -> Result { + let req_msg = json!({ + "username": username, + "password": password, + "clientToken": token, + "agent": { + "name": "Minecraft", + "version": 1 + }}); + let req = serde_json::to_string(&req_msg)?; + + let client = reqwest::blocking::Client::new(); + let res = client.post(LOGIN_URL) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(req) + .send()?; + + let ret: serde_json::Value = serde_json::from_reader(res)?; + if let Some(error) = ret.get("error").and_then(|v| v.as_str()) { + return Err(super::Error::Err(format!( + "{}: {}", + error, + ret.get("errorMessage").and_then(|v| v.as_str()).unwrap()) + )); + } + Ok(Profile { + username: ret.pointer("/selectedProfile/name").and_then(|v| v.as_str()).unwrap().to_owned(), + id: ret.pointer("/selectedProfile/id").and_then(|v| v.as_str()).unwrap().to_owned(), + access_token: ret.get("accessToken").and_then(|v| v.as_str()).unwrap().to_owned(), + }) + } + + pub fn refresh(self, token: &str) -> Result { + let req_msg = json!({ + "accessToken": self.access_token.clone(), + "clientToken": token + }); + let req = serde_json::to_string(&req_msg)?; + + let client = reqwest::blocking::Client::new(); + let res = client.post(VALIDATE_URL) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(req) + .send()?; + + if res.status() != reqwest::StatusCode::NO_CONTENT { + let req = serde_json::to_string(&req_msg)?; // TODO: fix parsing twice to avoid move + // Refresh needed + let res = client.post(REFRESH_URL) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(req) + .send()?; + + let ret: serde_json::Value = serde_json::from_reader(res)?; + if let Some(error) = ret.get("error").and_then(|v| v.as_str()) { + return Err(super::Error::Err(format!( + "{}: {}", + error, + ret.get("errorMessage").and_then(|v| v.as_str()).unwrap()) + )); + } + return Ok(Profile { + username: ret.pointer("/selectedProfile/name").and_then(|v| v.as_str()).unwrap().to_owned(), + id: ret.pointer("/selectedProfile/id").and_then(|v| v.as_str()).unwrap().to_owned(), + access_token: ret.get("accessToken").and_then(|v| v.as_str()).unwrap().to_owned(), + }); + } + Ok(self) + } + + pub fn join_server(&self, server_id: &str, shared_key: &[u8], public_key: &[u8]) -> Result<(), super::Error> { + let mut hasher = sha1::Sha1::new(); + hasher.input(server_id.as_bytes()); + hasher.input(shared_key); + hasher.input(public_key); + let mut hash = hasher.result(); + + // Mojang uses a hex method which allows for + // negatives so we have to account for that. + let negative = (hash[0] & 0x80) == 0x80; + if negative { + twos_compliment(&mut hash); + } + let hash_str = hash.iter().map(|b| format!("{:02x}", b)).collect::>().join(""); + let hash_val = hash_str.trim_start_matches('0'); + let hash_str = if negative { + "-".to_owned() + &hash_val[..] + } else { + hash_val.to_owned() + }; + + let join_msg = json!({ + "accessToken": &self.access_token, + "selectedProfile": &self.id, + "serverId": hash_str + }); + let join = serde_json::to_string(&join_msg).unwrap(); + + let client = reqwest::blocking::Client::new(); + let res = client.post(JOIN_URL) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(join) + .send()?; + + if res.status() == reqwest::StatusCode::NO_CONTENT { + Ok(()) + } else { + Err(super::Error::Err("Failed to auth with server".to_owned())) + } + } + + pub fn is_complete(&self) -> bool { + !self.username.is_empty() && !self.id.is_empty() && !self.access_token.is_empty() + } +} + +fn twos_compliment(data: &mut [u8]) { + let mut carry = true; + for i in (0..data.len()).rev() { + data[i] = !data[i]; + if carry { + carry = data[i] == 0xFF; + data[i] = data[i].wrapping_add(1); + } + } +} diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs new file mode 100644 index 0000000..a1acce9 --- /dev/null +++ b/protocol/src/protocol/packet.rs @@ -0,0 +1,2967 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::format; + +state_packets!( + handshake Handshaking { + serverbound Serverbound { + /// Handshake is the first packet sent in the protocol. + /// Its used for deciding if the request is a client + /// is requesting status information about the server + /// (MOTD, players etc) or trying to login to the server. + /// + /// The host and port fields are not used by the vanilla + /// server but are there for virtual server hosting to + /// be able to redirect a client to a target server with + /// a single address + port. + /// + /// Some modified servers/proxies use the handshake field + /// differently, packing information into the field other + /// than the hostname due to the protocol not providing + /// any system for custom information to be transfered + /// by the client to the server until after login. + packet Handshake { + /// The protocol version of the connecting client + field protocol_version: VarInt =, + /// The hostname the client connected to + field host: String =, + /// The port the client connected to + field port: u16 =, + /// The next protocol state the client wants + field next: VarInt =, + } + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + /// TeleportConfirm is sent by the client as a reply to a telport from + /// the server. + packet TeleportConfirm { + field teleport_id: VarInt =, + } + packet QueryBlockNBT { + field transaction_id: VarInt =, + field location: Position =, + } + packet SetDifficulty { + field new_difficulty: u8 =, + } + /// TabComplete is sent by the client when the client presses tab in + /// the chat box. + packet TabComplete { + field text: String =, + field assume_command: bool =, + field has_target: bool =, + field target: Option = when(|p: &TabComplete| p.has_target), + } + packet TabComplete_NoAssume { + field text: String =, + field has_target: bool =, + field target: Option = when(|p: &TabComplete_NoAssume| p.has_target), + } + packet TabComplete_NoAssume_NoTarget { + field text: String =, + } + /// ChatMessage is sent by the client when it sends a chat message or + /// executes a command (prefixed by '/'). + packet ChatMessage { + field message: String =, + } + /// ClientStatus is sent to update the client's status + packet ClientStatus { + field action_id: VarInt =, + } + packet ClientStatus_u8 { + field action_id: u8=, + } + /// ClientSettings is sent by the client to update its current settings. + packet ClientSettings { + field locale: String =, + field view_distance: u8 =, + field chat_mode: VarInt =, + field chat_colors: bool =, + field displayed_skin_parts: u8 =, + field main_hand: VarInt =, + } + packet ClientSettings_u8 { + field locale: String =, + field view_distance: u8 =, + field chat_mode: u8 =, + field chat_colors: bool =, + field displayed_skin_parts: u8 =, + field main_hand: VarInt =, + } + packet ClientSettings_u8_Handsfree { + field locale: String =, + field view_distance: u8 =, + field chat_mode: u8 =, + field chat_colors: bool =, + field displayed_skin_parts: u8 =, + } + packet ClientSettings_u8_Handsfree_Difficulty { + field locale: String =, + field view_distance: u8 =, + field chat_mode: u8 =, + field chat_colors: bool =, + field difficulty: u8 =, + field displayed_skin_parts: u8 =, + } + /// ConfirmTransactionServerbound is a reply to ConfirmTransaction. + packet ConfirmTransactionServerbound { + field id: u8 =, + field action_number: i16 =, + field accepted: bool =, + } + /// EnchantItem is sent when the client enchants an item. + packet EnchantItem { + field id: u8 =, + field enchantment: u8 =, + } + /// ClickWindowButton is used for clicking an enchantment, lectern, stonecutter, or loom. + packet ClickWindowButton { + field id: u8 =, + field button: u8 =, + } + /// ClickWindow is sent when the client clicks in a window. + packet ClickWindow { + field id: u8 =, + field slot: i16 =, + field button: u8 =, + field action_number: u16 =, + field mode: VarInt =, + field clicked_item: Option =, + } + packet ClickWindow_u8 { + field id: u8 =, + field slot: i16 =, + field button: u8 =, + field action_number: u16 =, + field mode: u8 =, + field clicked_item: Option =, + } + /// CloseWindow is sent when the client closes a window. + packet CloseWindow { + field 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. + packet PluginMessageServerbound { + field channel: String =, + field data: Vec =, + } + packet PluginMessageServerbound_i16 { + field channel: String =, + field data: LenPrefixedBytes =, + } + packet EditBook { + field new_book: Option =, + field is_signing: bool =, + field hand: VarInt =, + } + packet QueryEntityNBT { + field transaction_id: VarInt =, + field entity_id: VarInt =, + } + /// UseEntity is sent when the user interacts (right clicks) or attacks + /// (left clicks) an entity. + packet UseEntity { + field target_id: VarInt =, + field ty: VarInt =, + field target_x: f32 = when(|p: &UseEntity| p.ty.0 == 2), + field target_y: f32 = when(|p: &UseEntity| p.ty.0 == 2), + field target_z: f32 = when(|p: &UseEntity| p.ty.0 == 2), + field hand: VarInt = when(|p: &UseEntity| p.ty.0 == 0 || p.ty.0 == 2), + } + packet UseEntity_Handsfree { + field target_id: VarInt =, + field ty: VarInt =, + field target_x: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), + field target_y: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), + field target_z: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), + } + packet UseEntity_Handsfree_i32 { + field target_id: i32 =, + field ty: u8 =, + } + /// 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_i64 { + field id: i64 =, + } + packet KeepAliveServerbound_VarInt { + field id: VarInt =, + } + packet KeepAliveServerbound_i32 { + field id: i32 =, + } + packet LockDifficulty { + field locked: bool =, + } + /// PlayerPosition is used to update the player's position. + packet PlayerPosition { + field x: f64 =, + field y: f64 =, + field z: f64 =, + field on_ground: bool =, + } + packet PlayerPosition_HeadY { + field x: f64 =, + field feet_y: f64 =, + field head_y: f64 =, + field z: f64 =, + field on_ground: bool =, + } + /// PlayerPositionLook is a combination of PlayerPosition and + /// PlayerLook. + packet PlayerPositionLook { + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + field on_ground: bool =, + } + packet PlayerPositionLook_HeadY { + field x: f64 =, + field feet_y: f64 =, + field head_y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + field on_ground: bool =, + } + /// PlayerLook is used to update the player's rotation. + packet PlayerLook { + field yaw: f32 =, + field pitch: f32 =, + field on_ground: bool =, + } + /// Player is used to update whether the player is on the ground or not. + packet Player { + field on_ground: bool =, + } + /// Sent by the client when in a vehicle instead of the normal move packet. + packet VehicleMove { + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + } + /// SteerBoat is used to visually update the boat paddles. + packet SteerBoat { + field left_paddle_turning: bool =, + field right_paddle_turning: bool =, + } + packet PickItem { + field slot_to_use: VarInt =, + } + /// CraftRecipeRequest is sent when player clicks a recipe in the crafting book. + packet CraftRecipeRequest { + field window_id: u8 =, + field recipe: VarInt =, + field make_all: bool =, + } + /// ClientAbilities is used to modify the players current abilities. + /// Currently flying is the only one + packet ClientAbilities { + field flags: u8 =, + field flying_speed: f32 =, + field 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. + packet PlayerDigging { + field status: VarInt =, + field location: Position =, + field face: u8 =, + } + packet PlayerDigging_u8 { + field status: u8 =, + field location: Position =, + field face: u8 =, + } + packet PlayerDigging_u8_u8y { + field status: u8 =, + field x: i32 =, + field y: u8 =, + field z: i32 =, + field face: u8 =, + } + /// PlayerAction is sent when a player preforms various actions. + packet PlayerAction { + field entity_id: VarInt =, + field action_id: VarInt =, + field jump_boost: VarInt =, + } + packet PlayerAction_i32 { + field entity_id: i32 =, + field action_id: i8 =, + field jump_boost: i32 =, + } + /// SteerVehicle is sent by the client when steers or preforms an action + /// on a vehicle. + packet SteerVehicle { + field sideways: f32 =, + field forward: f32 =, + field flags: u8 =, + } + packet SteerVehicle_jump_unmount { + field sideways: f32 =, + field forward: f32 =, + field jump: bool =, + field unmount: bool =, + } + /// CraftingBookData is sent when the player interacts with the crafting book. + packet CraftingBookData { + field action: VarInt =, + field recipe_id: i32 = when(|p: &CraftingBookData| p.action.0 == 0), + field crafting_book_open: bool = when(|p: &CraftingBookData| p.action.0 == 1), + field crafting_filter: bool = when(|p: &CraftingBookData| p.action.0 == 1), + } + packet NameItem { + field item_name: String =, + } + /// ResourcePackStatus informs the server of the client's current progress + /// in activating the requested resource pack + packet ResourcePackStatus { + field result: VarInt =, + } + packet ResourcePackStatus_hash { + field hash: String =, + field result: VarInt =, + } + // TODO: Document + packet AdvancementTab { + field action: VarInt =, + field tab_id: String = when(|p: &AdvancementTab| p.action.0 == 0), + } + packet SelectTrade { + field selected_slot: VarInt =, + } + packet SetBeaconEffect { + field primary_effect: VarInt =, + field secondary_effect: VarInt =, + } + /// HeldItemChange is sent when the player changes the currently active + /// hotbar slot. + packet HeldItemChange { + field slot: i16 =, + } + packet UpdateCommandBlock { + field location: Position =, + field command: String =, + field mode: VarInt =, + field flags: u8 =, + } + packet UpdateCommandBlockMinecart { + field entity_id: VarInt =, + field command: String =, + field track_output: bool =, + } + /// CreativeInventoryAction is sent when the client clicks in the creative + /// inventory. This is used to spawn items in creative. + packet CreativeInventoryAction { + field slot: i16 =, + field clicked_item: Option =, + } + packet UpdateJigsawBlock { + field location: Position =, + field attachment_type: String =, + field target_pool: String =, + field final_state: String =, + } + packet UpdateStructureBlock { + field location: Position =, + field action: VarInt =, + field mode: VarInt =, + field name: String =, + field offset_x: i8 =, + field offset_y: i8 =, + field offset_z: i8 =, + field size_x: i8 =, + field size_y: i8 =, + field size_z: i8 =, + field mirror: VarInt =, + field rotation: VarInt =, + field metadata: String =, + field integrity: f32 =, + field seed: VarLong =, + field flags: i8 =, + } + /// SetSign sets the text on a sign after placing it. + packet SetSign { + field location: Position =, + field line1: String =, + field line2: String =, + field line3: String =, + field line4: String =, + } + packet SetSign_i16y { + field x: i32 =, + field y: i16 =, + field z: i32 =, + field line1: String =, + field line2: String =, + field line3: String =, + field line4: String =, + } + /// ArmSwing is sent by the client when the player left clicks (to swing their + /// arm). + packet ArmSwing { + field hand: VarInt =, + } + packet ArmSwing_Handsfree { + field empty: () =, + } + packet ArmSwing_Handsfree_ID { + field entity_id: i32 =, + field animation: u8 =, + } + /// SpectateTeleport is sent by clients in spectator mode to teleport to a player. + packet SpectateTeleport { + field target: UUID =, + } + /// PlayerBlockPlacement is sent when the client tries to place a block. + packet PlayerBlockPlacement_f32 { + field location: Position =, + field face: VarInt =, + field hand: VarInt =, + field cursor_x: f32 =, + field cursor_y: f32 =, + field cursor_z: f32 =, + } + packet PlayerBlockPlacement_u8 { + field location: Position =, + field face: VarInt =, + field hand: VarInt =, + field cursor_x: u8 =, + field cursor_y: u8 =, + field cursor_z: u8 =, + } + packet PlayerBlockPlacement_u8_Item { + field location: Position =, + field face: u8 =, + field hand: Option =, + field cursor_x: u8 =, + field cursor_y: u8 =, + field cursor_z: u8 =, + } + packet PlayerBlockPlacement_u8_Item_u8y { + field x: i32 =, + field y: u8 =, + field z: i32 =, + field face: u8 =, + field hand: Option =, + field cursor_x: u8 =, + field cursor_y: u8 =, + field cursor_z: u8 =, + } + + /// UseItem is sent when the client tries to use an item. + packet UseItem { + field hand: VarInt =, + } + } + clientbound Clientbound { + /// SpawnObject is used to spawn an object or vehicle into the world when it + /// is in range of the client. + packet SpawnObject { + field entity_id: VarInt =, + field uuid: UUID =, + field ty: u8 =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field pitch: i8 =, + field yaw: i8 =, + field data: i32 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + } + packet SpawnObject_i32 { + field entity_id: VarInt =, + field uuid: UUID =, + field ty: u8 =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field pitch: i8 =, + field yaw: i8 =, + field data: i32 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + } + packet SpawnObject_i32_NoUUID { + field entity_id: VarInt =, + field ty: u8 =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field pitch: i8 =, + field yaw: i8 =, + field data: i32 =, + field velocity_x: i16 = when(|p: &SpawnObject_i32_NoUUID| p.data != 0), + field velocity_y: i16 = when(|p: &SpawnObject_i32_NoUUID| p.data != 0), + field velocity_z: i16 = when(|p: &SpawnObject_i32_NoUUID| p.data != 0), + } + /// 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. + packet SpawnExperienceOrb { + field entity_id: VarInt =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field count: i16 =, + } + packet SpawnExperienceOrb_i32 { + field entity_id: VarInt =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field count: i16 =, + } + /// SpawnGlobalEntity spawns an entity which is visible from anywhere in the + /// world. Currently only used for lightning. + packet SpawnGlobalEntity { + field entity_id: VarInt =, + field ty: u8 =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + } + packet SpawnGlobalEntity_i32 { + field entity_id: VarInt =, + field ty: u8 =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + } + /// SpawnMob is used to spawn a living entity into the world when it is in + /// range of the client. + packet SpawnMob_NoMeta { + field entity_id: VarInt =, + field uuid: UUID =, + field ty: VarInt =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: i8 =, + field pitch: i8 =, + field head_pitch: i8 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + } + packet SpawnMob_WithMeta { + field entity_id: VarInt =, + field uuid: UUID =, + field ty: VarInt =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: i8 =, + field pitch: i8 =, + field head_pitch: i8 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + field metadata: types::Metadata =, + } + packet SpawnMob_u8 { + field entity_id: VarInt =, + field uuid: UUID =, + field ty: u8 =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: i8 =, + field pitch: i8 =, + field head_pitch: i8 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + field metadata: types::Metadata =, + } + packet SpawnMob_u8_i32 { + field entity_id: VarInt =, + field uuid: UUID =, + field ty: u8 =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field head_pitch: i8 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + field metadata: types::Metadata =, + } + packet SpawnMob_u8_i32_NoUUID { + field entity_id: VarInt =, + field ty: u8 =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field head_pitch: i8 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + field 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. + packet SpawnPainting_VarInt { + field entity_id: VarInt =, + field uuid: UUID =, + field motive: VarInt =, + field location: Position =, + field direction: u8 =, + } + packet SpawnPainting_String { + field entity_id: VarInt =, + field uuid: UUID =, + field title: String =, + field location: Position =, + field direction: u8 =, + } + packet SpawnPainting_NoUUID { + field entity_id: VarInt =, + field title: String =, + field location: Position =, + field direction: u8 =, + } + packet SpawnPainting_NoUUID_i32 { + field entity_id: VarInt =, + field title: String =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field direction: i32 =, + } + /// 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. + packet SpawnPlayer_f64_NoMeta { + field entity_id: VarInt =, + field uuid: UUID =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: i8 =, + field pitch: i8 =, + } + packet SpawnPlayer_f64 { + field entity_id: VarInt =, + field uuid: UUID =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: i8 =, + field pitch: i8 =, + field metadata: types::Metadata =, + } + packet SpawnPlayer_i32 { + field entity_id: VarInt =, + field uuid: UUID =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field metadata: types::Metadata =, + } + packet SpawnPlayer_i32_HeldItem { + field entity_id: VarInt =, + field uuid: UUID =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field current_item: u16 =, + field metadata: types::Metadata =, + } + packet SpawnPlayer_i32_HeldItem_String { + field entity_id: VarInt =, + field uuid: String =, + field name: String =, + field properties: LenPrefixed =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field current_item: u16 =, + field metadata: types::Metadata =, + } + + /// Animation is sent by the server to play an animation on a specific entity. + packet Animation { + field entity_id: VarInt =, + field animation_id: u8 =, + } + /// Statistics is used to update the statistics screen for the client. + packet Statistics { + field statistices: LenPrefixed =, + } + /// BlockBreakAnimation is used to create and update the block breaking + /// animation played when a player starts digging a block. + packet BlockBreakAnimation { + field entity_id: VarInt =, + field location: Position =, + field stage: i8 =, + } + packet BlockBreakAnimation_i32 { + field entity_id: VarInt =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field stage: i8 =, + } + /// UpdateBlockEntity updates the nbt tag of a block entity in the + /// world. + packet UpdateBlockEntity { + field location: Position =, + field action: u8 =, + field nbt: Option =, + } + packet UpdateBlockEntity_Data { + field x: i32 =, + field y: i16 =, + field z: i32 =, + field action: u8 =, + field data_length: i16 =, + field gzipped_nbt: Vec =, + } + /// BlockAction triggers different actions depending on the target block. + packet BlockAction { + field location: Position =, + field byte1: u8 =, + field byte2: u8 =, + field block_type: VarInt =, + } + packet BlockAction_u16 { + field x: i32 =, + field y: u16 =, + field z: i32 =, + field byte1: u8 =, + field byte2: u8 =, + field block_type: VarInt =, + } + /// BlockChange is used to update a single block on the client. + packet BlockChange_VarInt { + field location: Position =, + field block_id: VarInt =, + } + packet BlockChange_u8 { + field x: i32 =, + field y: u8 =, + field z: i32 =, + field block_id: VarInt =, + field block_metadata: u8 =, + } + /// 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. + packet BossBar { + field uuid: UUID =, + field action: VarInt =, + field title: format::Component = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 3), + field health: f32 = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 2), + field color: VarInt = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 4), + field style: VarInt = when(|p: &BossBar| p.action.0 == 0 || p.action.0 == 4), + field 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. + packet ServerDifficulty { + field difficulty: u8 =, + } + packet ServerDifficulty_Locked { + field difficulty: u8 =, + field locked: bool =, + } + /// TabCompleteReply is sent as a reply to a tab completion request. + /// The matches should be possible completions for the command/chat the + /// player sent. + packet TabCompleteReply { + field matches: LenPrefixed =, + } + packet DeclareCommands { + field nodes: LenPrefixed =, + field root_index: VarInt =, + } + /// 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. + packet ServerMessage { + field message: format::Component =, + /// 0 - Chat message, 1 - System message, 2 - Action bar message + field position: u8 =, + } + packet ServerMessage_NoPosition { + field message: format::Component =, + } + /// MultiBlockChange is used to update a batch of blocks in a single packet. + packet MultiBlockChange_VarInt { + field chunk_x: i32 =, + field chunk_z: i32 =, + field records: LenPrefixed =, + } + packet MultiBlockChange_u16 { + field chunk_x: i32 =, + field chunk_z: i32 =, + field record_count: u16 =, + field data_size: i32 =, + field data: Vec =, + } + /// ConfirmTransaction notifies the client whether a transaction was successful + /// or failed (e.g. due to lag). + packet ConfirmTransaction { + field id: u8 =, + field action_number: i16 =, + field accepted: bool =, + } + /// WindowClose forces the client to close the window with the given id, + /// e.g. a chest getting destroyed. + packet WindowClose { + field 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. + packet WindowOpen { + field id: u8 =, + field ty: String =, + field title: format::Component =, + field slot_count: u8 =, + field entity_id: i32 = when(|p: &WindowOpen| p.ty == "EntityHorse"), + } + packet WindowOpenHorse { + field window_id: u8 =, + field number_of_slots: VarInt =, + field entity_id: i32 =, + } + packet WindowOpen_u8 { + field id: u8 =, + field ty: u8 =, + field title: format::Component =, + field slot_count: u8 =, + field use_provided_window_title: bool =, + field entity_id: i32 = when(|p: &WindowOpen_u8| p.ty == 11), + } + packet WindowOpen_VarInt { + field id: VarInt =, + field ty: VarInt =, + field title: format::Component =, + } + /// WindowItems sets every item in a window. + packet WindowItems { + field id: u8 =, + field items: LenPrefixed> =, + } + /// WindowProperty changes the value of a property of a window. Properties + /// vary depending on the window type. + packet WindowProperty { + field id: u8 =, + field property: i16 =, + field value: i16 =, + } + /// WindowSetSlot changes an itemstack in one of the slots in a window. + packet WindowSetSlot { + field id: u8 =, + field property: i16 =, + field item: Option =, + } + /// SetCooldown disables a set item (by id) for the set number of ticks + packet SetCooldown { + field item_id: VarInt =, + field 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. + packet PluginMessageClientbound { + field channel: String =, + field data: Vec =, + } + packet PluginMessageClientbound_i16 { + field channel: String =, + field data: LenPrefixedBytes =, + } + /// Plays a sound by name on the client + packet NamedSoundEffect { + field name: String =, + field category: VarInt =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field volume: f32 =, + field pitch: f32 =, + } + packet NamedSoundEffect_u8 { + field name: String =, + field category: VarInt =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field volume: f32 =, + field pitch: u8 =, + } + packet NamedSoundEffect_u8_NoCategory { + field name: String =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field volume: f32 =, + field pitch: u8 =, + } + /// Disconnect causes the client to disconnect displaying the passed reason. + packet Disconnect { + field reason: format::Component =, + } + /// EntityAction causes an entity to preform an action based on the passed + /// id. + packet EntityAction { + field entity_id: i32 =, + field action_id: u8 =, + } + /// Explosion is sent when an explosion is triggered (tnt, creeper etc). + /// This plays the effect and removes the effected blocks. + packet Explosion { + field x: f32 =, + field y: f32 =, + field z: f32 =, + field radius: f32 =, + field records: LenPrefixed =, + field velocity_x: f32 =, + field velocity_y: f32 =, + field velocity_z: f32 =, + } + /// ChunkUnload tells the client to unload the chunk at the specified + /// position. + packet ChunkUnload { + field x: i32 =, + field z: i32 =, + } + /// SetCompression updates the compression threshold. + packet SetCompression { + field threshold: VarInt =, + } + /// ChangeGameState is used to modify the game's state like gamemode or + /// weather. + packet ChangeGameState { + field reason: u8 =, + field 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. + packet KeepAliveClientbound_i64 { + field id: i64 =, + } + packet KeepAliveClientbound_VarInt { + field id: VarInt =, + } + packet KeepAliveClientbound_i32 { + field id: i32 =, + } + /// ChunkData sends or updates a single chunk on the client. If New is set + /// then biome data should be sent too. + packet ChunkData_Biomes3D { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: VarInt =, + field heightmaps: Option =, + field biomes: Biomes3D = when(|p: &ChunkData_Biomes3D| p.new), + field data: LenPrefixedBytes =, + field block_entities: LenPrefixed> =, + } + packet ChunkData_HeightMap { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: VarInt =, + field heightmaps: Option =, + field data: LenPrefixedBytes =, + field block_entities: LenPrefixed> =, + } + packet ChunkData { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: VarInt =, + field data: LenPrefixedBytes =, + field block_entities: LenPrefixed> =, + } + packet ChunkData_NoEntities { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: VarInt =, + field data: LenPrefixedBytes =, + } + packet ChunkData_NoEntities_u16 { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: u16 =, + field data: LenPrefixedBytes =, + } + packet ChunkData_17 { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: u16 =, + field add_bitmask: u16 =, + field compressed_data: LenPrefixedBytes =, + } + packet ChunkDataBulk { + field skylight: bool =, + field chunk_meta: LenPrefixed =, + field chunk_data: Vec =, + } + packet ChunkDataBulk_17 { + field chunk_column_count: u16 =, + field data_length: i32 =, + field skylight: bool =, + field chunk_data_and_meta: Vec =, + } + /// 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. + packet Effect { + field effect_id: i32 =, + field location: Position =, + field data: i32 =, + field disable_relative: bool =, + } + packet Effect_u8y { + field effect_id: i32 =, + field x: i32 =, + field y: u8 =, + field z: i32 =, + field data: i32 =, + field disable_relative: bool =, + } + /// Particle spawns particles at the target location with the various + /// modifiers. + packet Particle_f64 { + field particle_id: i32 =, + field long_distance: bool =, + field x: f64 =, + field y: f64=, + field z: f64 =, + field offset_x: f32 =, + field offset_y: f32 =, + field offset_z: f32 =, + field speed: f32 =, + field count: i32 =, + field block_state: VarInt = when(|p: &Particle_f64| p.particle_id == 3 || p.particle_id == 20), + field red: f32 = when(|p: &Particle_f64| p.particle_id == 11), + field green: f32 = when(|p: &Particle_f64| p.particle_id == 11), + field blue: f32 = when(|p: &Particle_f64| p.particle_id == 11), + field scale: f32 = when(|p: &Particle_f64| p.particle_id == 11), + field item: Option = when(|p: &Particle_f64| p.particle_id == 27), + } + packet Particle_Data { + field particle_id: i32 =, + field long_distance: bool =, + field x: f32 =, + field y: f32 =, + field z: f32 =, + field offset_x: f32 =, + field offset_y: f32 =, + field offset_z: f32 =, + field speed: f32 =, + field count: i32 =, + field block_state: VarInt = when(|p: &Particle_Data| p.particle_id == 3 || p.particle_id == 20), + field red: f32 = when(|p: &Particle_Data| p.particle_id == 11), + field green: f32 = when(|p: &Particle_Data| p.particle_id == 11), + field blue: f32 = when(|p: &Particle_Data| p.particle_id == 11), + field scale: f32 = when(|p: &Particle_Data| p.particle_id == 11), + field item: Option = when(|p: &Particle_Data| p.particle_id == 27), + } + packet Particle_VarIntArray { + field particle_id: i32 =, + field long_distance: bool =, + field x: f32 =, + field y: f32 =, + field z: f32 =, + field offset_x: f32 =, + field offset_y: f32 =, + field offset_z: f32 =, + field speed: f32 =, + field count: i32 =, + field data1: VarInt = when(|p: &Particle_VarIntArray| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38 || p.particle_id == 46), + field data2: VarInt = when(|p: &Particle_VarIntArray| p.particle_id == 36), + } + packet Particle_Named { + field particle_id: String =, + field x: f32 =, + field y: f32 =, + field z: f32 =, + field offset_x: f32 =, + field offset_y: f32 =, + field offset_z: f32 =, + field speed: f32 =, + field count: i32 =, + } + /// JoinGame is sent after completing the login process. This + /// sets the initial state for the client. + packet JoinGame_HashedSeed_Respawn { + /// The entity id the client will be referenced by + field entity_id: i32 =, + /// The starting gamemode of the client + field gamemode: u8 =, + /// The dimension the client is starting in + field dimension: i32 =, + /// Truncated SHA-256 hash of world's seed + field hashed_seed: i64 =, + /// The max number of players on the server + field max_players: u8 =, + /// The level type of the server + field level_type: String =, + /// The render distance (2-32) + field view_distance: VarInt =, + /// Whether the client should reduce the amount of debug + /// information it displays in F3 mode + field reduced_debug_info: bool =, + /// Whether to prompt or immediately respawn + field enable_respawn_screen: bool =, + } + packet JoinGame_i32_ViewDistance { + /// The entity id the client will be referenced by + field entity_id: i32 =, + /// The starting gamemode of the client + field gamemode: u8 =, + /// The dimension the client is starting in + field dimension: i32 =, + /// The max number of players on the server + field max_players: u8 =, + /// The level type of the server + field level_type: String =, + /// The render distance (2-32) + field view_distance: VarInt =, + /// Whether the client should reduce the amount of debug + /// information it displays in F3 mode + field reduced_debug_info: bool =, + } + packet JoinGame_i32 { + /// The entity id the client will be referenced by + field entity_id: i32 =, + /// The starting gamemode of the client + field gamemode: u8 =, + /// The dimension the client is starting in + field dimension: i32 =, + /// The difficuilty setting for the server + field difficulty: u8 =, + /// The max number of players on the server + field max_players: u8 =, + /// The level type of the server + field level_type: String =, + /// Whether the client should reduce the amount of debug + /// information it displays in F3 mode + field reduced_debug_info: bool =, + } + packet JoinGame_i8 { + /// The entity id the client will be referenced by + field entity_id: i32 =, + /// The starting gamemode of the client + field gamemode: u8 =, + /// The dimension the client is starting in + field dimension: i8 =, + /// The difficuilty setting for the server + field difficulty: u8 =, + /// The max number of players on the server + field max_players: u8 =, + /// The level type of the server + field level_type: String =, + /// Whether the client should reduce the amount of debug + /// information it displays in F3 mode + field reduced_debug_info: bool =, + } + packet JoinGame_i8_NoDebug { + field entity_id: i32 =, + field gamemode: u8 =, + field dimension: i8 =, + field difficulty: u8 =, + field max_players: u8 =, + field level_type: String =, + } + /// Maps updates a single map's contents + packet Maps { + field item_damage: VarInt =, + field scale: i8 =, + field tracking_position: bool =, + field icons: LenPrefixed =, + field columns: u8 =, + field rows: Option = when(|p: &Maps| p.columns > 0), + field x: Option = when(|p: &Maps| p.columns > 0), + field z: Option = when(|p: &Maps| p.columns > 0), + field data: Option> = when(|p: &Maps| p.columns > 0), + } + packet Maps_NoTracking { + field item_damage: VarInt =, + field scale: i8 =, + field icons: LenPrefixed =, + field columns: u8 =, + field rows: Option = when(|p: &Maps_NoTracking| p.columns > 0), + field x: Option = when(|p: &Maps_NoTracking| p.columns > 0), + field z: Option = when(|p: &Maps_NoTracking| p.columns > 0), + field data: Option> = when(|p: &Maps_NoTracking| p.columns > 0), + } + packet Maps_NoTracking_Data { + field item_damage: VarInt =, + field data: LenPrefixedBytes =, + } + /// EntityMove moves the entity with the id by the offsets provided. + packet EntityMove_i16 { + field entity_id: VarInt =, + field delta_x: FixedPoint12 =, + field delta_y: FixedPoint12 =, + field delta_z: FixedPoint12 =, + field on_ground: bool =, + } + packet EntityMove_i8 { + field entity_id: VarInt =, + field delta_x: FixedPoint5 =, + field delta_y: FixedPoint5 =, + field delta_z: FixedPoint5 =, + field on_ground: bool =, + } + packet EntityMove_i8_i32_NoGround { + field entity_id: i32 =, + field delta_x: FixedPoint5 =, + field delta_y: FixedPoint5 =, + field delta_z: FixedPoint5 =, + } + /// EntityLookAndMove is a combination of EntityMove and EntityLook. + packet EntityLookAndMove_i16 { + field entity_id: VarInt =, + field delta_x: FixedPoint12 =, + field delta_y: FixedPoint12 =, + field delta_z: FixedPoint12 =, + field yaw: i8 =, + field pitch: i8 =, + field on_ground: bool =, + } + packet EntityLookAndMove_i8 { + field entity_id: VarInt =, + field delta_x: FixedPoint5 =, + field delta_y: FixedPoint5 =, + field delta_z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field on_ground: bool =, + } + packet EntityLookAndMove_i8_i32_NoGround { + field entity_id: i32 =, + field delta_x: FixedPoint5 =, + field delta_y: FixedPoint5 =, + field delta_z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + } + /// EntityLook rotates the entity to the new angles provided. + packet EntityLook_VarInt { + field entity_id: VarInt =, + field yaw: i8 =, + field pitch: i8 =, + field on_ground: bool =, + } + packet EntityLook_i32_NoGround { + field entity_id: i32 =, + field yaw: i8 =, + field pitch: i8 =, + } + /// Entity does nothing. It is a result of subclassing used in Minecraft. + packet Entity { + field entity_id: VarInt =, + } + packet Entity_i32 { + field entity_id: i32 =, + } + /// EntityUpdateNBT updates the entity named binary tag. + packet EntityUpdateNBT { + field entity_id: VarInt =, + field nbt: Option =, + } + /// Teleports the player's vehicle + packet VehicleTeleport { + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + } + /// Opens the book GUI. + packet OpenBook { + field hand: 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. + packet SignEditorOpen { + field location: Position =, + } + packet SignEditorOpen_i32 { + field x: i32 =, + field y: i32 =, + field z: i32 =, + } + /// CraftRecipeResponse is a response to CraftRecipeRequest, notifies the UI. + packet CraftRecipeResponse { + field window_id: u8 =, + field recipe: VarInt =, + } + /// PlayerAbilities is used to modify the players current abilities. Flying, + /// creative, god mode etc. + packet PlayerAbilities { + field flags: u8 =, + field flying_speed: f32 =, + field walking_speed: f32 =, + } + /// CombatEvent is used for... you know, I never checked. I have no + /// clue. + packet CombatEvent { + field event: VarInt =, + field direction: Option = when(|p: &CombatEvent| p.event.0 == 1), + field player_id: Option = when(|p: &CombatEvent| p.event.0 == 2), + field entity_id: Option = when(|p: &CombatEvent| p.event.0 == 1 || p.event.0 == 2), + field 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. + packet PlayerInfo { + field inner: packet::PlayerInfoData =, + } + packet PlayerInfo_String { + field name: String =, + field online: bool =, + field ping: u16 =, + } + packet FacePlayer { + field feet_eyes: VarInt =, + field target_x: f64 =, + field target_y: f64 =, + field target_z: f64 =, + field is_entity: bool =, + field entity_id: Option = when(|p: &FacePlayer| p.is_entity), + field entity_feet_eyes: Option = when(|p: &FacePlayer| p.is_entity), + } + /// 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. + packet TeleportPlayer_WithConfirm { + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + field flags: u8 =, + field teleport_id: VarInt =, + } + packet TeleportPlayer_NoConfirm { + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + field flags: u8 =, + } + packet TeleportPlayer_OnGround { + field x: f64 =, + field eyes_y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + field on_ground: bool =, + } + /// EntityUsedBed is sent by the server when a player goes to bed. + packet EntityUsedBed { + field entity_id: VarInt =, + field location: Position =, + } + packet EntityUsedBed_i32 { + field entity_id: i32 =, + field x: i32 =, + field y: u8 =, + field z: i32 =, + } + packet UnlockRecipes_NoSmelting { + field action: VarInt =, + field crafting_book_open: bool =, + field filtering_craftable: bool =, + field recipe_ids: LenPrefixed =, + field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes_NoSmelting| p.action.0 == 0), + } + packet UnlockRecipes_WithSmelting { + field action: VarInt =, + field crafting_book_open: bool =, + field filtering_craftable: bool =, + field smelting_book_open: bool =, + field filtering_smeltable: bool =, + field recipe_ids: LenPrefixed =, + field recipe_ids2: LenPrefixed = when(|p: &UnlockRecipes_WithSmelting| p.action.0 == 0), + } + /// EntityDestroy destroys the entities with the ids in the provided slice. + packet EntityDestroy { + field entity_ids: LenPrefixed =, + } + packet EntityDestroy_u8 { + field entity_ids: LenPrefixed =, + } + /// EntityRemoveEffect removes an effect from an entity. + packet EntityRemoveEffect { + field entity_id: VarInt =, + field effect_id: i8 =, + } + packet EntityRemoveEffect_i32 { + field entity_id: i32 =, + field 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. + packet ResourcePackSend { + field url: String =, + field hash: String =, + } + /// Respawn is sent to respawn the player after death or when they move worlds. + packet Respawn { + field dimension: i32 =, + field difficulty: u8 =, + field gamemode: u8 =, + field level_type: String =, + } + packet Respawn_HashedSeed { + field dimension: i32 =, + field hashed_seed: i64 =, + field difficulty: u8 =, + field gamemode: u8 =, + field level_type: String =, + } + /// EntityHeadLook rotates an entity's head to the new angle. + packet EntityHeadLook { + field entity_id: VarInt =, + field head_yaw: i8 =, + } + packet EntityHeadLook_i32 { + field entity_id: i32 =, + field head_yaw: i8 =, + } + packet EntityStatus { + field entity_id: i32 =, + field entity_status: i8 =, + } + packet NBTQueryResponse { + field transaction_id: VarInt =, + field nbt: Option =, + } + /// SelectAdvancementTab indicates the client should switch the advancement tab. + packet SelectAdvancementTab { + field has_id: bool =, + field tab_id: String = when(|p: &SelectAdvancementTab| p.has_id), + } + /// WorldBorder configures the world's border. + packet WorldBorder { + field action: VarInt =, + field old_radius: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1), + field new_radius: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1 || p.action.0 == 0), + field speed: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 1), + field x: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 2), + field z: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 2), + field portal_boundary: Option = when(|p: &WorldBorder| p.action.0 == 3), + field warning_time: Option = when(|p: &WorldBorder| p.action.0 == 3 || p.action.0 == 4), + field 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. + packet Camera { + field target_id: VarInt =, + } + /// SetCurrentHotbarSlot changes the player's currently selected hotbar item. + packet SetCurrentHotbarSlot { + field slot: u8 =, + } + /// UpdateViewPosition is used to determine what chunks should be remain loaded. + packet UpdateViewPosition { + field chunk_x: VarInt =, + field chunk_z: VarInt =, + } + /// UpdateViewDistance is sent by the integrated server when changing render distance. + packet UpdateViewDistance { + field view_distance: VarInt =, + } + /// ScoreboardDisplay is used to set the display position of a scoreboard. + packet ScoreboardDisplay { + field position: u8 =, + field name: String =, + } + /// EntityMetadata updates the metadata for an entity. + packet EntityMetadata { + field entity_id: VarInt =, + field metadata: types::Metadata =, + } + packet EntityMetadata_i32 { + field entity_id: i32 =, + field metadata: types::Metadata =, + } + /// EntityAttach attaches to entities together, either by mounting or leashing. + /// -1 can be used at the EntityID to deattach. + packet EntityAttach { + field entity_id: i32 =, + field vehicle: i32 =, + } + packet EntityAttach_leashed { + field entity_id: i32 =, + field vehicle: i32 =, + field leash: bool =, + } + /// EntityVelocity sets the velocity of an entity in 1/8000 of a block + /// per a tick. + packet EntityVelocity { + field entity_id: VarInt =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + } + packet EntityVelocity_i32 { + field entity_id: i32 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field 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. + packet EntityEquipment { + field entity_id: VarInt =, + field slot: VarInt =, + field item: Option =, + } + packet EntityEquipment_u16 { + field entity_id: VarInt =, + field slot: u16 =, + field item: Option =, + } + packet EntityEquipment_u16_i32 { + field entity_id: i32 =, + field slot: u16 =, + field item: Option =, + } + /// SetExperience updates the experience bar on the client. + packet SetExperience { + field experience_bar: f32 =, + field level: VarInt =, + field total_experience: VarInt =, + } + packet SetExperience_i16 { + field experience_bar: f32 =, + field level: i16 =, + field total_experience: i16 =, + } + /// UpdateHealth is sent by the server to update the player's health and food. + packet UpdateHealth { + field health: f32 =, + field food: VarInt =, + field food_saturation: f32 =, + } + packet UpdateHealth_u16 { + field health: f32 =, + field food: u16 =, + field food_saturation: f32 =, + } + /// ScoreboardObjective creates/updates a scoreboard objective. + packet ScoreboardObjective { + field name: String =, + field mode: u8 =, + field value: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), + field ty: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), + } + packet ScoreboardObjective_NoMode { + field name: String =, + field value: String =, + field ty: u8 =, + } + /// SetPassengers mounts entities to an entity + packet SetPassengers { + field entity_id: VarInt =, + field passengers: LenPrefixed =, + } + /// Teams creates and updates teams + packet Teams_VarInt { + field name: String =, + field mode: u8 =, + field display_name: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field flags: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field name_tag_visibility: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field collision_rule: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field formatting: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field prefix: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field suffix: Option = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 2), + field players: Option> = when(|p: &Teams_VarInt| p.mode == 0 || p.mode == 3 || p.mode == 4), + } + packet Teams_u8 { + field name: String =, + field mode: u8 =, + field data: Vec =, + field display_name: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field prefix: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field suffix: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field flags: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field name_tag_visibility: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field collision_rule: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field color: Option = when(|p: &Teams_u8| p.mode == 0 || p.mode == 2), + field players: Option> = when(|p: &Teams_u8| p.mode == 0 || p.mode == 3 || p.mode == 4), + } + packet Teams_NoVisColor { + field name: String =, + field mode: u8 =, + field display_name: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field prefix: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field suffix: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field flags: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field players: Option> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 3 || p.mode == 4), + } + /// UpdateScore is used to update or remove an item from a scoreboard + /// objective. + packet UpdateScore { + field name: String =, + field action: u8 =, + field object_name: String =, + field value: Option = when(|p: &UpdateScore| p.action != 1), + } + packet UpdateScore_i32 { + field name: String =, + field action: u8 =, + field object_name: String =, + field value: Option = when(|p: &UpdateScore_i32| p.action != 1), + } + /// SpawnPosition is sent to change the player's current spawn point. Currently + /// only used by the client for the compass. + packet SpawnPosition { + field location: Position =, + } + packet SpawnPosition_i32 { + field x: i32 =, + field y: i32 =, + field z: i32 =, + } + /// 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 send this now and again + packet TimeUpdate { + field world_age: i64 =, + field time_of_day: i64 =, + } + packet StopSound { + field flags: u8 =, + field source: Option = when(|p: &StopSound| p.flags & 0x01 != 0), + field sound: Option = when(|p: &StopSound| p.flags & 0x02 != 0), + } + /// Title configures an on-screen title. + packet Title { + field action: VarInt =, + field title: Option = when(|p: &Title| p.action.0 == 0), + field sub_title: Option = when(|p: &Title| p.action.0 == 1), + field action_bar_text: Option = when(|p: &Title| p.action.0 == 2), + field fade_in: Option = when(|p: &Title| p.action.0 == 3), + field fade_stay: Option = when(|p: &Title| p.action.0 == 3), + field fade_out: Option = when(|p: &Title| p.action.0 == 3), + } + packet Title_notext { + field action: VarInt =, + field title: Option = when(|p: &Title_notext| p.action.0 == 0), + field sub_title: Option = when(|p: &Title_notext| p.action.0 == 1), + field fade_in: Option = when(|p: &Title_notext| p.action.0 == 2), + field fade_stay: Option = when(|p: &Title_notext| p.action.0 == 2), + field fade_out: Option = when(|p: &Title_notext| p.action.0 == 2), + } + packet Title_notext_component { + field action: VarInt =, + field title: Option = when(|p: &Title_notext_component| p.action.0 == 0), + field sub_title: Option = when(|p: &Title_notext_component| p.action.0 == 1), + field fade_in: Option = when(|p: &Title_notext_component| p.action.0 == 2), + field fade_stay: Option = when(|p: &Title_notext_component| p.action.0 == 2), + field fade_out: Option = when(|p: &Title_notext_component| p.action.0 == 2), + } + /// UpdateSign sets or changes the text on a sign. + packet UpdateSign { + field location: Position =, + field line1: format::Component =, + field line2: format::Component =, + field line3: format::Component =, + field line4: format::Component =, + } + packet UpdateSign_u16 { + field x: i32 =, + field y: u16 =, + field z: i32 =, + field line1: format::Component =, + field line2: format::Component =, + field line3: format::Component =, + field line4: format::Component =, + } + /// SoundEffect plays the named sound at the target location. + packet SoundEffect { + field name: VarInt =, + field category: VarInt =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field volume: f32 =, + field pitch: f32 =, + } + packet SoundEffect_u8 { + field name: VarInt =, + field category: VarInt =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field volume: f32 =, + field pitch: u8 =, + } + /// Plays a sound effect from an entity. + packet EntitySoundEffect { + field sound_id: VarInt =, + field sound_category: VarInt =, + field entity_id: VarInt =, + field volume: f32 =, + field pitch: f32 =, + } + /// PlayerListHeaderFooter updates the header/footer of the player list. + packet PlayerListHeaderFooter { + field header: format::Component =, + field footer: format::Component =, + } + /// CollectItem causes the collected item to fly towards the collector. This + /// does not destroy the entity. + packet CollectItem { + field collected_entity_id: VarInt =, + field collector_entity_id: VarInt =, + field number_of_items: VarInt =, + } + packet CollectItem_nocount { + field collected_entity_id: VarInt =, + field collector_entity_id: VarInt =, + } + packet CollectItem_nocount_i32 { + field collected_entity_id: i32 =, + field collector_entity_id: i32 =, + } + /// EntityTeleport teleports the entity to the target location. This is + /// sent if the entity moves further than EntityMove allows. + packet EntityTeleport_f64 { + field entity_id: VarInt =, + field x: f64 =, + field y: f64 =, + field z: f64 =, + field yaw: i8 =, + field pitch: i8 =, + field on_ground: bool =, + } + packet EntityTeleport_i32 { + field entity_id: VarInt =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + field on_ground: bool =, + } + packet EntityTeleport_i32_i32_NoGround { + field entity_id: i32 =, + field x: FixedPoint5 =, + field y: FixedPoint5 =, + field z: FixedPoint5 =, + field yaw: i8 =, + field pitch: i8 =, + } + packet Advancements { + field data: Vec =, + /* TODO: fix parsing modded advancements 1.12.2 (e.g. SevTech Ages) + * see https://github.com/iceiix/stevenarella/issues/148 + field reset_clear: bool =, + field mapping: LenPrefixed =, + field identifiers: LenPrefixed =, + field progress: LenPrefixed =, + */ + } + /// EntityProperties updates the properties for an entity. + packet EntityProperties { + field entity_id: VarInt =, + field properties: LenPrefixed =, + } + packet EntityProperties_i32 { + field entity_id: i32 =, + field properties: LenPrefixed =, + } + /// EntityEffect applies a status effect to an entity for a given duration. + packet EntityEffect { + field entity_id: VarInt =, + field effect_id: i8 =, + field amplifier: i8 =, + field duration: VarInt =, + field hide_particles: bool =, + } + packet EntityEffect_i32 { + field entity_id: i32 =, + field effect_id: i8 =, + field amplifier: i8 =, + field duration: i16 =, + } + packet DeclareRecipes { + field recipes: LenPrefixed =, + } + packet Tags { + field block_tags: LenPrefixed =, + field item_tags: LenPrefixed =, + field fluid_tags: LenPrefixed =, + } + packet TagsWithEntities { + field block_tags: LenPrefixed =, + field item_tags: LenPrefixed =, + field fluid_tags: LenPrefixed =, + field entity_tags: LenPrefixed =, + } + packet AcknowledgePlayerDigging { + field location: Position =, + field block: VarInt =, + field status: VarInt =, + field successful: bool =, + } + packet UpdateLight { + field chunk_x: VarInt =, + field chunk_z: VarInt =, + field sky_light_mask: VarInt =, + field block_light_mask: VarInt =, + field empty_sky_light_mask: VarInt =, + field light_arrays: Vec =, + } + packet TradeList_WithoutRestock { + field id: VarInt =, + field trades: LenPrefixed =, + field villager_level: VarInt =, + field experience: VarInt =, + field is_regular_villager: bool =, + } + packet TradeList_WithRestock { + field id: VarInt =, + field trades: LenPrefixed =, + field villager_level: VarInt =, + field experience: VarInt =, + field is_regular_villager: bool =, + field can_restock: bool =, + } + packet CoFHLib_SendUUID { + field player_uuid: UUID =, + } + } + } + login Login { + serverbound Serverbound { + /// 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. + packet LoginStart { + field username: String =, + } + /// EncryptionResponse is sent as a reply to EncryptionRequest. All + /// packets following this one must be encrypted with AES/CFB8 + /// encryption. + packet EncryptionResponse { + /// The key for the AES/CFB8 cipher encrypted with the + /// public key + field shared_secret: LenPrefixedBytes =, + /// The verify token from the request encrypted with the + /// public key + field verify_token: LenPrefixedBytes =, + } + packet EncryptionResponse_i16 { + field shared_secret: LenPrefixedBytes =, + field verify_token: LenPrefixedBytes =, + } + packet LoginPluginResponse { + field message_id: VarInt =, + field successful: bool =, + field data: Vec =, + } + } + 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). + packet LoginDisconnect { + field 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. + packet EncryptionRequest { + /// Generally empty, left in from legacy auth + /// but is still used by the client if provided + field server_id: String =, + /// A RSA Public key serialized in x.509 PRIX format + field public_key: LenPrefixedBytes =, + /// Token used by the server to verify encryption is working + /// correctly + field verify_token: LenPrefixedBytes =, + } + packet EncryptionRequest_i16 { + field server_id: String =, + field public_key: LenPrefixedBytes =, + field 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). + packet LoginSuccess { + /// String encoding of a uuid (with hyphens) + field uuid: String =, + field username: String =, + } + /// SetInitialCompression sets the compression threshold during the + /// login state. + packet SetInitialCompression { + /// Threshold where a packet should be sent compressed + field threshold: VarInt =, + } + packet LoginPluginRequest { + field message_id: VarInt =, + field channel: String =, + field data: Vec =, + } + } + } + status Status { + serverbound Serverbound { + /// StatusRequest is sent by the client instantly after + /// switching to the Status protocol state and is used + /// to signal the server to send a StatusResponse to the + /// client + packet StatusRequest { + field 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. + packet StatusPing { + field ping: i64 =, + } + } + clientbound Clientbound { + /// StatusResponse is sent as a reply to a StatusRequest. + /// The Status should contain a json encoded structure with + /// version information, a player sample, a description/MOTD + /// and optionally a favicon. + // + /// The structure is as follows + /// + /// ```json + /// { + /// "version": { + /// "name": "1.8.3", + /// "protocol": 47, + /// }, + /// "players": { + /// "max": 20, + /// "online": 1, + /// "sample": [ + /// packet {"name": "Thinkofdeath", "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"} + /// ] + /// }, + /// "description": "Hello world", + /// "favicon": "data:image/png;base64," + /// } + /// ``` + packet StatusResponse { + field 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. + packet StatusPong { + field ping: i64 =, + } + } + } +); + +#[derive(Debug, Default)] +pub struct SpawnProperty { + pub name: String, + pub value: String, + pub signature: String, +} + +impl Serializable for SpawnProperty { + fn read_from(buf: &mut R) -> Result { + Ok(SpawnProperty { + name: Serializable::read_from(buf)?, + value: Serializable::read_from(buf)?, + signature: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.name.write_to(buf)?; + self.value.write_to(buf)?; + self.signature.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct Statistic { + pub name: String, + pub value: VarInt, +} + +impl Serializable for Statistic { + fn read_from(buf: &mut R) -> Result { + Ok(Statistic { + name: Serializable::read_from(buf)?, + value: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + 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 R) -> Result { + Ok(BlockChangeRecord { + xz: Serializable::read_from(buf)?, + y: Serializable::read_from(buf)?, + block_id: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.xz.write_to(buf)?; + self.y.write_to(buf)?; + self.block_id.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct ChunkMeta { + pub x: i32, + pub z: i32, + pub bitmask: u16, +} + +impl Serializable for ChunkMeta { + fn read_from(buf: &mut R) -> Result { + Ok(ChunkMeta { + x: Serializable::read_from(buf)?, + z: Serializable::read_from(buf)?, + bitmask: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.x.write_to(buf)?; + self.z.write_to(buf)?; + self.bitmask.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 R) -> Result { + Ok(ExplosionRecord { + x: Serializable::read_from(buf)?, + y: Serializable::read_from(buf)?, + z: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.x.write_to(buf)?; + 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 R) -> Result { + Ok(MapIcon { + direction_type: Serializable::read_from(buf)?, + x: Serializable::read_from(buf)?, + z: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.direction_type.write_to(buf)?; + self.x.write_to(buf)?; + self.z.write_to(buf) + } +} + +impl Default for MapIcon { + fn default() -> Self { + MapIcon { + direction_type: 0, + x: 0, + z: 0, + } + } +} + +#[derive(Debug, Default)] +pub struct Advancement { + pub id: String, + pub parent_id: Option, + pub display_data: Option, + pub criteria: LenPrefixed, + pub requirements: LenPrefixed>, +} + +impl Serializable for Advancement { + fn read_from(buf: &mut R) -> Result { + let id: String = Serializable::read_from(buf)?; + let parent_id = { + let has_parent: u8 = Serializable::read_from(buf)?; + if has_parent != 0 { + let parent_id: String = Serializable::read_from(buf)?; + Some(parent_id) + } else { + None + } + }; + + let has_display: u8 = Serializable::read_from(buf)?; + let display_data = { + if has_display != 0 { + let display_data: AdvancementDisplay = Serializable::read_from(buf)?; + Some(display_data) + } else { + None + } + }; + + let criteria: LenPrefixed = Serializable::read_from(buf)?; + let requirements: LenPrefixed> = Serializable::read_from(buf)?; + Ok(Advancement { + id, + parent_id, + display_data, + criteria, + requirements, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.id.write_to(buf)?; + self.parent_id.write_to(buf)?; + self.display_data.write_to(buf)?; + self.criteria.write_to(buf)?; + self.requirements.write_to(buf) + } + +} + +#[derive(Debug, Default)] +pub struct AdvancementDisplay { + pub title: String, + pub description: String, + pub icon: Option, + pub frame_type: VarInt, + pub flags: i32, + pub background_texture: Option, + pub x_coord: f32, + pub y_coord: f32, +} + +impl Serializable for AdvancementDisplay { + fn read_from(buf: &mut R) -> Result { + let title: String = Serializable::read_from(buf)?; + let description: String = Serializable::read_from(buf)?; + let icon: Option = Serializable::read_from(buf)?; + let frame_type: VarInt = Serializable::read_from(buf)?; + let flags: i32 = Serializable::read_from(buf)?; + let background_texture: Option = if flags & 1 != 0 { + Serializable::read_from(buf)? + } else { + None + }; + let x_coord: f32 = Serializable::read_from(buf)?; + let y_coord: f32 = Serializable::read_from(buf)?; + + Ok(AdvancementDisplay { + title, + description, + icon, + frame_type, + flags, + background_texture, + x_coord, + y_coord, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.title.write_to(buf)?; + self.description.write_to(buf)?; + self.icon.write_to(buf)?; + self.frame_type.write_to(buf)?; + self.flags.write_to(buf)?; + if self.flags & 1 != 0 { + self.background_texture.write_to(buf)?; + } + self.x_coord.write_to(buf)?; + self.y_coord.write_to(buf) + } + +} + +#[derive(Debug, Default)] +pub struct AdvancementProgress { + pub id: String, + pub criteria: LenPrefixed, +} + +impl Serializable for AdvancementProgress { + fn read_from(buf: &mut R) -> Result { + Ok(AdvancementProgress { + id: Serializable::read_from(buf)?, + criteria: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.id.write_to(buf)?; + self.criteria.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct CriterionProgress { + pub id: String, + pub date_of_achieving: Option, +} + +impl Serializable for CriterionProgress { + fn read_from(buf: &mut R) -> Result { + let id = Serializable::read_from(buf)?; + let achieved: u8 = Serializable::read_from(buf)?; + let date_of_achieving: Option = if achieved != 0 { + Serializable::read_from(buf)? + } else { + None + }; + + Ok(CriterionProgress { + id, + date_of_achieving, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.id.write_to(buf)?; + self.date_of_achieving.write_to(buf) + } +} + + + +#[derive(Debug, Default)] +pub struct EntityProperty { + pub key: String, + pub value: f64, + pub modifiers: LenPrefixed, +} + +impl Serializable for EntityProperty { + fn read_from(buf: &mut R) -> Result { + Ok(EntityProperty { + key: Serializable::read_from(buf)?, + value: Serializable::read_from(buf)?, + modifiers: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.key.write_to(buf)?; + self.value.write_to(buf)?; + self.modifiers.write_to(buf) + } +} + +#[derive(Debug, Default)] +pub struct EntityProperty_i16 { + pub key: String, + pub value: f64, + pub modifiers: LenPrefixed, +} + +impl Serializable for EntityProperty_i16 { + fn read_from(buf: &mut R) -> Result { + Ok(EntityProperty_i16 { + key: Serializable::read_from(buf)?, + value: Serializable::read_from(buf)?, + modifiers: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.key.write_to(buf)?; + 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 R) -> Result { + Ok(PropertyModifier { + uuid: Serializable::read_from(buf)?, + amount: Serializable::read_from(buf)?, + operation: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.uuid.write_to(buf)?; + 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 R) -> Result { + let mut m = PlayerInfoData { + action: Serializable::read_from(buf)?, + players: Vec::new(), + }; + let len = VarInt::read_from(buf)?; + for _ in 0..len.0 { + let uuid = UUID::read_from(buf)?; + match m.action.0 { + 0 => { + let name = String::read_from(buf)?; + let mut props = Vec::new(); + let plen = VarInt::read_from(buf)?.0; + for _ in 0..plen { + let mut prop = PlayerProperty { + name: String::read_from(buf)?, + value: String::read_from(buf)?, + signature: Default::default(), + }; + if bool::read_from(buf)? { + prop.signature = Some(String::read_from(buf)?); + } + props.push(prop); + } + let p = PlayerDetail::Add { + uuid, + name, + properties: props, + gamemode: Serializable::read_from(buf)?, + ping: Serializable::read_from(buf)?, + display: { + if bool::read_from(buf)? { + Some(Serializable::read_from(buf)?) + } else { + None + } + }, + }; + m.players.push(p); + } + 1 => { + m.players.push(PlayerDetail::UpdateGamemode { + uuid, + gamemode: Serializable::read_from(buf)?, + }) + } + 2 => { + m.players.push(PlayerDetail::UpdateLatency { + uuid, + ping: Serializable::read_from(buf)?, + }) + } + 3 => { + m.players.push(PlayerDetail::UpdateDisplayName { + uuid, + display: { + if bool::read_from(buf)? { + Some(Serializable::read_from(buf)?) + } else { + None + } + }, + }) + } + 4 => { + m.players.push(PlayerDetail::Remove { uuid: uuid }) + } + _ => panic!(), + } + } + Ok(m) + } + + fn write_to(&self, _: &mut W) -> Result<(), 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, +} + +use crate::item; +type RecipeIngredient = LenPrefixed>; + +#[derive(Debug)] +pub enum RecipeData { + Shapeless { + group: String, + ingredients: LenPrefixed, + result: Option, + }, + Shaped { + width: VarInt, + height: VarInt, + group: String, + ingredients: Vec, + result: Option, + }, + ArmorDye, + BookCloning, + MapCloning, + MapExtending, + FireworkRocket, + FireworkStar, + FireworkStarFade, + RepairItem, + TippedArrow, + BannerDuplicate, + BannerAddPattern, + ShieldDecoration, + ShulkerBoxColoring, + SuspiciousStew, + Smelting { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Blasting { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Smoking { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Campfire { + group: String, + ingredient: RecipeIngredient, + result: Option, + experience: f32, + cooking_time: VarInt, + }, + Stonecutting { + group: String, + ingredient: RecipeIngredient, + result: Option, + }, +} + +impl Default for RecipeData { + fn default() -> Self { + RecipeData::ArmorDye + } +} + +#[derive(Debug, Default)] +pub struct Recipe { + pub id: String, + pub ty: String, + pub data: RecipeData, +} + +impl Serializable for Recipe { + fn read_from(buf: &mut R) -> Result { + let (id, ty) = { + let a = String::read_from(buf)?; + let b = String::read_from(buf)?; + + let protocol_version = super::current_protocol_version(); + + // 1.14+ swaps recipe identifier and type, and adds namespace to type + if protocol_version >= 477 { + let ty = a; + let id = b; + + if ty.find(':').is_some() { + (id, ty) + } else { + (id, format!("minecraft:{}", ty)) + } + } else { + let ty = b; + let id = a; + (id, format!("minecraft:{}", ty)) + } + }; + + let data = + match ty.as_ref() { + "minecraft:crafting_shapeless" => RecipeData::Shapeless { + group: Serializable::read_from(buf)?, + ingredients: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + }, + "minecraft:crafting_shaped" => { + let width: VarInt = Serializable::read_from(buf)?; + let height: VarInt = Serializable::read_from(buf)?; + let group: String = Serializable::read_from(buf)?; + + let capacity = width.0 as usize * height.0 as usize; + + let mut ingredients = Vec::with_capacity(capacity); + for _ in 0 .. capacity { + ingredients.push(Serializable::read_from(buf)?); + } + let result: Option = Serializable::read_from(buf)?; + + RecipeData::Shaped { width, height, group, ingredients, result } + } + "minecraft:crafting_special_armordye" => RecipeData::ArmorDye, + "minecraft:crafting_special_bookcloning" => RecipeData::BookCloning, + "minecraft:crafting_special_mapcloning" => RecipeData::MapCloning, + "minecraft:crafting_special_mapextending" => RecipeData::MapExtending, + "minecraft:crafting_special_firework_rocket" => RecipeData::FireworkRocket, + "minecraft:crafting_special_firework_star" => RecipeData::FireworkStar, + "minecraft:crafting_special_firework_star_fade" => RecipeData::FireworkStarFade, + "minecraft:crafting_special_repairitem" => RecipeData::RepairItem, + "minecraft:crafting_special_tippedarrow" => RecipeData::TippedArrow, + "minecraft:crafting_special_bannerduplicate" => RecipeData::BannerDuplicate, + "minecraft:crafting_special_banneraddpattern" => RecipeData::BannerAddPattern, + "minecraft:crafting_special_shielddecoration" => RecipeData::ShieldDecoration, + "minecraft:crafting_special_shulkerboxcoloring" => RecipeData::ShulkerBoxColoring, + "minecraft:crafting_special_suspiciousstew" => RecipeData::SuspiciousStew, + "minecraft:smelting" => RecipeData::Smelting { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, + "minecraft:blasting" => RecipeData::Blasting { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, + "minecraft:smoking" => RecipeData::Smoking { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, + "minecraft:campfire" | "minecraft:campfire_cooking" => RecipeData::Campfire { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + experience: Serializable::read_from(buf)?, + cooking_time: Serializable::read_from(buf)?, + }, + "minecraft:stonecutting" => RecipeData::Stonecutting { + group: Serializable::read_from(buf)?, + ingredient: Serializable::read_from(buf)?, + result: Serializable::read_from(buf)?, + }, + _ => panic!("unrecognized recipe type: {}", ty) + }; + + Ok(Recipe { id, ty, data }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + +#[derive(Debug, Default)] +pub struct Tags { + pub tag_name: String, + pub entries: LenPrefixed, +} + +impl Serializable for Tags { + fn read_from(buf: &mut R) -> Result { + Ok(Tags { + tag_name: Serializable::read_from(buf)?, + entries: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + +#[derive(Debug, Default)] +pub struct Trade { + pub input_item_1: Option, + pub output_item: Option, + pub has_second_item: bool, + pub input_item_2: Option, + pub trades_disabled: bool, + pub tool_uses: i32, + pub max_trade_uses: i32, + pub xp: i32, + pub special_price: i32, + pub price_multiplier: f32, + pub demand: Option, +} + +impl Serializable for Trade { + fn read_from(buf: &mut R) -> Result { + let protocol_version = super::current_protocol_version(); + + Ok(Trade { + input_item_1: Serializable::read_from(buf)?, + output_item: Serializable::read_from(buf)?, + has_second_item: Serializable::read_from(buf)?, + input_item_2: Serializable::read_from(buf)?, + trades_disabled: Serializable::read_from(buf)?, + tool_uses: Serializable::read_from(buf)?, + max_trade_uses: Serializable::read_from(buf)?, + xp: Serializable::read_from(buf)?, + special_price: Serializable::read_from(buf)?, + price_multiplier: Serializable::read_from(buf)?, + demand: if protocol_version >= 498 { Some(Serializable::read_from(buf)?) } else { None }, + }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + + +#[derive(Debug, Default)] +pub struct CommandNode { + pub flags: u8, + pub children: LenPrefixed, + pub redirect_node: Option, + pub name: Option, + pub parser: Option, + pub properties: Option, + pub suggestions_type: Option, +} + +#[derive(Debug, Eq, PartialEq)] +enum CommandNodeType { + Root, + Literal, + Argument, +} + +#[derive(Debug)] +pub enum CommandProperty { + Bool, + Double { + flags: u8, + min: Option, + max: Option, + }, + Float { + flags: u8, + min: Option, + max: Option, + }, + Integer { + flags: u8, + min: Option, + max: Option, + }, + String { + token_type: VarInt, + }, + Entity { + flags: u8, + }, + GameProfile, + BlockPos, + ColumnPos, + Time, + Vec3, + Vec2, + BlockState, + BlockPredicate, + ItemStack, + ItemPredicate, + Color, + Component, + Message, + Nbt, + NbtPath, + NbtTag, + NbtCompoundTag, + Objective, + ObjectiveCriteria, + Operation, + Particle, + Rotation, + ScoreboardSlot, + ScoreHolder { + flags: u8, + }, + Swizzle, + Team, + ItemSlot, + ResourceLocation, + MobEffect, + Function, + EntityAnchor, + Range { + decimals: bool, + }, + IntRange, + FloatRange, + ItemEnchantment, + EntitySummon, + Dimension, +} + + +impl Serializable for CommandNode { + fn read_from(buf: &mut R) -> Result { + let flags: u8 = Serializable::read_from(buf)?; + let children: LenPrefixed = Serializable::read_from(buf)?; + + let node_type = match flags & 0x03 { + 0 => CommandNodeType::Root, + 1 => CommandNodeType::Literal, + 2 => CommandNodeType::Argument, + _ => panic!("unrecognized command node type {}", flags & 0x03), + }; + let _is_executable = flags & 0x04 != 0; + let has_redirect = flags & 0x08 != 0; + let has_suggestions_type = flags & 0x10 != 0; + + let redirect_node: Option = if has_redirect { + Some(Serializable::read_from(buf)?) + } else { + None + }; + + let name: Option = if node_type == CommandNodeType::Argument || node_type == CommandNodeType::Literal { + Serializable::read_from(buf)? + } else { + None + }; + let parser: Option = if node_type == CommandNodeType::Argument { + Serializable::read_from(buf)? + } else { + None + }; + + let properties: Option = if let Some(ref parse) = parser { + Some(match parse.as_ref() { + "brigadier:bool" => CommandProperty::Bool, + "brigadier:double" => { + let flags = Serializable::read_from(buf)?; + let min = if flags & 0x01 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + let max = if flags & 0x02 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + CommandProperty::Double { flags, min, max } + }, + "brigadier:float" => { + let flags = Serializable::read_from(buf)?; + let min = if flags & 0x01 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + let max = if flags & 0x02 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + CommandProperty::Float { flags, min, max } + }, + "brigadier:integer" => { + let flags = Serializable::read_from(buf)?; + let min = if flags & 0x01 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + let max = if flags & 0x02 != 0 { Some(Serializable::read_from(buf)?) } else { None }; + CommandProperty::Integer { flags, min, max } + }, + "brigadier:string" => { + CommandProperty::String { token_type: Serializable::read_from(buf)? } + }, + "minecraft:entity" => { + CommandProperty::Entity { flags: Serializable::read_from(buf)? } + }, + "minecraft:game_profile" => CommandProperty::GameProfile, + "minecraft:block_pos" => CommandProperty::BlockPos, + "minecraft:column_pos" => CommandProperty::ColumnPos, + "minecraft:time" => CommandProperty::Time, + "minecraft:vec3" => CommandProperty::Vec3, + "minecraft:vec2" => CommandProperty::Vec2, + "minecraft:block_state" => CommandProperty::BlockState, + "minecraft:block_predicate" => CommandProperty::BlockPredicate, + "minecraft:item_stack" => CommandProperty::ItemStack, + "minecraft:item_predicate" => CommandProperty::ItemPredicate, + "minecraft:color" => CommandProperty::Color, + "minecraft:component" => CommandProperty::Component, + "minecraft:message" => CommandProperty::Message, + "minecraft:nbt" => CommandProperty::Nbt, + "minecraft:nbt_path" => CommandProperty::NbtPath, + "minecraft:nbt_tag" => CommandProperty::NbtTag, + "minecraft:nbt_compound_tag" => CommandProperty::NbtCompoundTag, + "minecraft:objective" => CommandProperty::Objective, + "minecraft:objective_criteria" => CommandProperty::ObjectiveCriteria, + "minecraft:operation" => CommandProperty::Operation, + "minecraft:particle" => CommandProperty::Particle, + "minecraft:rotation" => CommandProperty::Rotation, + "minecraft:scoreboard_slot" => CommandProperty::ScoreboardSlot, + "minecraft:score_holder" => { + CommandProperty::ScoreHolder { flags: Serializable::read_from(buf)? } + }, + "minecraft:swizzle" => CommandProperty::Swizzle, + "minecraft:team" => CommandProperty::Team, + "minecraft:item_slot" => CommandProperty::ItemSlot, + "minecraft:resource_location" => CommandProperty::ResourceLocation, + "minecraft:mob_effect" => CommandProperty::MobEffect, + "minecraft:function" => CommandProperty::Function, + "minecraft:entity_anchor" => CommandProperty::EntityAnchor, + "minecraft:range" => { + CommandProperty::Range { decimals: Serializable::read_from(buf)? } + }, + "minecraft:int_range" => CommandProperty::IntRange, + "minecraft:float_range" => CommandProperty::FloatRange, + "minecraft:item_enchantment" => CommandProperty::ItemEnchantment, + "minecraft:entity_summon" => CommandProperty::EntitySummon, + "minecraft:dimension" => CommandProperty::Dimension, + _ => panic!("unsupported command node parser {}", parse), + }) + } else { + None + }; + + let suggestions_type: Option = if has_suggestions_type { + Serializable::read_from(buf)? + } else { + None + }; + + Ok(CommandNode { flags, children, redirect_node, name, parser, properties, suggestions_type }) + } + + fn write_to(&self, _: &mut W) -> Result<(), Error> { + unimplemented!() + } +} + + diff --git a/protocol/src/protocol/versions.rs b/protocol/src/protocol/versions.rs new file mode 100644 index 0000000..461bca1 --- /dev/null +++ b/protocol/src/protocol/versions.rs @@ -0,0 +1,77 @@ +use super::*; + +mod v1_15_1; +mod v1_14_4; +mod v1_14_3; +mod v1_14_2; +mod v1_14_1; +mod v1_14; +mod v19w02a; +mod v18w50a; +mod v1_13_2; +mod v1_12_2; +mod v1_11_2; +mod v1_10_2; +mod v1_9_2; +mod v1_9; +mod v15w39c; +mod v1_8_9; +mod v1_7_10; + +// https://wiki.vg/Protocol_History +// https://wiki.vg/Protocol_version_numbers#Versions_after_the_Netty_rewrite + +pub fn protocol_name_to_protocol_version(s: String) -> i32 { + match s.as_ref() { + "" => SUPPORTED_PROTOCOLS[0], + "1.15.1" => 575, + "1.14.4" => 498, + "1.14.3" => 490, + "1.14.2" => 485, + "1.14.1" => 480, + "1.14" => 477, + "19w02a" => 452, + "18w50a" => 451, + "1.13.2" => 404, + "1.12.2" => 340, + "1.11.2" => 316, + "1.11" => 315, + "1.10.2" => 210, + "1.9.2" => 109, + "1.9" => 107, + "15w39c" => 74, + "1.8.9" => 47, + "1.7.10" => 5, + _ => { + if let Ok(n) = s.parse::() { + n + } else { + panic!("Unrecognized protocol name: {}", s) + } + } + } +} + +pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: Direction, id: i32, to_internal: bool) -> i32 { + match version { + 575 => v1_15_1::translate_internal_packet_id(state, dir, id, to_internal), + 498 => v1_14_4::translate_internal_packet_id(state, dir, id, to_internal), + 490 => v1_14_3::translate_internal_packet_id(state, dir, id, to_internal), + 485 => v1_14_2::translate_internal_packet_id(state, dir, id, to_internal), + 480 => v1_14_1::translate_internal_packet_id(state, dir, id, to_internal), + 477 => v1_14::translate_internal_packet_id(state, dir, id, to_internal), + 452 => v19w02a::translate_internal_packet_id(state, dir, id, to_internal), + 451 => v18w50a::translate_internal_packet_id(state, dir, id, to_internal), + 404 => v1_13_2::translate_internal_packet_id(state, dir, id, to_internal), + 340 => v1_12_2::translate_internal_packet_id(state, dir, id, to_internal), + 316 => v1_11_2::translate_internal_packet_id(state, dir, id, to_internal), + 315 => v1_11_2::translate_internal_packet_id(state, dir, id, to_internal), + 210 => v1_10_2::translate_internal_packet_id(state, dir, id, to_internal), + 109 => v1_9_2::translate_internal_packet_id(state, dir, id, to_internal), + 107 => v1_9::translate_internal_packet_id(state, dir, id, to_internal), + 74 => v15w39c::translate_internal_packet_id(state, dir, id, to_internal), + 47 => v1_8_9::translate_internal_packet_id(state, dir, id, to_internal), + 5 => v1_7_10::translate_internal_packet_id(state, dir, id, to_internal), + _ => panic!("unsupported protocol version: {}", version), + } +} diff --git a/protocol/src/protocol/versions/v15w39c.rs b/protocol/src/protocol/versions/v15w39c.rs new file mode 100644 index 0000000..9801571 --- /dev/null +++ b/protocol/src/protocol/versions/v15w39c.rs @@ -0,0 +1,141 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TabComplete_NoAssume + 0x01 => ChatMessage + 0x02 => ClientStatus + 0x03 => ClientSettings_u8 + 0x04 => ConfirmTransactionServerbound + 0x05 => EnchantItem + 0x06 => ClickWindow_u8 + 0x07 => CloseWindow + 0x08 => PluginMessageServerbound + 0x09 => UseEntity + 0x0a => KeepAliveServerbound_VarInt + 0x0b => PlayerPosition + 0x0c => PlayerPositionLook + 0x0d => PlayerLook + 0x0e => Player + 0x0f => ClientAbilities + 0x10 => PlayerDigging_u8 + 0x11 => PlayerAction + 0x12 => SteerVehicle + 0x13 => ResourcePackStatus + 0x14 => HeldItemChange + 0x15 => CreativeInventoryAction + 0x16 => SetSign + 0x17 => ArmSwing + 0x18 => SpectateTeleport + 0x19 => PlayerBlockPlacement_u8 + 0x1a => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject_i32 + 0x01 => SpawnExperienceOrb_i32 + 0x02 => SpawnGlobalEntity_i32 + 0x03 => SpawnMob_u8_i32 + 0x04 => SpawnPainting_NoUUID + 0x05 => SpawnPlayer_i32 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 0x11 => ConfirmTransaction + 0x12 => WindowClose + 0x13 => WindowOpen + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => Disconnect + 0x1a => EntityAction + 0x1b => Explosion + 0x1c => ChunkUnload + 0x1d => SetCompression + 0x1e => ChangeGameState + 0x1f => KeepAliveClientbound_VarInt + 0x20 => ChunkData_NoEntities + 0x21 => Effect + 0x22 => Particle_VarIntArray + 0x23 => NamedSoundEffect_u8_NoCategory + 0x24 => JoinGame_i8 + 0x25 => Maps + 0x26 => EntityMove_i8 + 0x27 => EntityLookAndMove_i8 + 0x28 => EntityLook_VarInt + 0x29 => Entity + 0x2a => SignEditorOpen + 0x2b => PlayerAbilities + 0x2c => CombatEvent + 0x2d => PlayerInfo + 0x2e => TeleportPlayer_NoConfirm + 0x2f => EntityUsedBed + 0x30 => EntityDestroy + 0x31 => EntityRemoveEffect + 0x32 => ResourcePackSend + 0x33 => Respawn + 0x34 => EntityHeadLook + 0x35 => WorldBorder + 0x36 => Camera + 0x37 => SetCurrentHotbarSlot + 0x38 => ScoreboardDisplay + 0x39 => EntityMetadata + 0x3a => EntityAttach_leashed + 0x3b => EntityVelocity + 0x3c => EntityEquipment + 0x3d => SetExperience + 0x3e => UpdateHealth + 0x3f => ScoreboardObjective + 0x40 => Teams_u8 + 0x41 => UpdateScore + 0x42 => SpawnPosition + 0x43 => TimeUpdate + 0x44 => Title_notext_component + 0x45 => UpdateSign + 0x46 => PlayerListHeaderFooter + 0x47 => CollectItem_nocount + 0x48 => EntityTeleport_i32 + 0x49 => EntityProperties + 0x4a => 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/v18w50a.rs b/protocol/src/protocol/versions/v18w50a.rs new file mode 100644 index 0000000..0a9d6a9 --- /dev/null +++ b/protocol/src/protocol/versions/v18w50a.rs @@ -0,0 +1,172 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => TabComplete + 0x06 => ConfirmTransactionServerbound + 0x07 => EnchantItem + 0x08 => ClickWindow + 0x09 => CloseWindow + 0x0a => PluginMessageServerbound + 0x0b => EditBook + 0x0c => QueryEntityNBT + 0x0d => UseEntity + 0x0e => KeepAliveServerbound_i64 + 0x0f => Player + 0x10 => PlayerPosition + 0x11 => PlayerPositionLook + 0x12 => PlayerLook + 0x13 => VehicleMove + 0x14 => SteerBoat + 0x15 => PickItem + 0x16 => CraftRecipeRequest + 0x17 => ClientAbilities + 0x18 => PlayerDigging + 0x19 => PlayerAction + 0x1a => SteerVehicle + 0x1b => CraftingBookData + 0x1c => NameItem + 0x1d => ResourcePackStatus + 0x1e => AdvancementTab + 0x1f => SelectTrade + 0x20 => SetBeaconEffect + 0x21 => HeldItemChange + 0x22 => UpdateCommandBlock + 0x23 => UpdateCommandBlockMinecart + 0x24 => CreativeInventoryAction + 0x25 => UpdateStructureBlock + 0x26 => SetSign + 0x27 => ArmSwing + 0x28 => SpectateTeleport + 0x29 => PlayerBlockPlacement_f32 + 0x2a => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_String + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowOpen + 0x15 => WindowItems + 0x16 => WindowProperty + 0x17 => WindowSetSlot + 0x18 => SetCooldown + 0x19 => PluginMessageClientbound + 0x1a => NamedSoundEffect + 0x1b => Disconnect + 0x1c => EntityAction + 0x1d => NBTQueryResponse + 0x1e => Explosion + 0x1f => ChunkUnload + 0x20 => ChangeGameState + 0x21 => KeepAliveClientbound_i64 + 0x22 => ChunkData_HeightMap + 0x23 => Effect + 0x24 => Particle_VarIntArray + 0x25 => JoinGame_i32 + 0x26 => Maps + 0x27 => Entity + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => VehicleTeleport + 0x2c => OpenBook + 0x2d => SignEditorOpen + 0x2e => CraftRecipeResponse + 0x2f => PlayerAbilities + 0x30 => CombatEvent + 0x31 => PlayerInfo + 0x32 => FacePlayer + 0x33 => TeleportPlayer_WithConfirm + 0x34 => EntityUsedBed + 0x35 => UnlockRecipes_WithSmelting + 0x36 => EntityDestroy + 0x37 => EntityRemoveEffect + 0x38 => ResourcePackSend + 0x39 => Respawn + 0x3a => EntityHeadLook + 0x3b => SelectAdvancementTab + 0x3c => WorldBorder + 0x3d => Camera + 0x3e => SetCurrentHotbarSlot + 0x3f => ScoreboardDisplay + 0x40 => EntityMetadata + 0x41 => EntityAttach + 0x42 => EntityVelocity + 0x43 => EntityEquipment + 0x44 => SetExperience + 0x45 => UpdateHealth + 0x46 => ScoreboardObjective + 0x47 => SetPassengers + 0x48 => Teams_u8 + 0x49 => UpdateScore + 0x4a => SpawnPosition + 0x4b => TimeUpdate + 0x4d => StopSound + 0x4e => SoundEffect + 0x4f => EntitySoundEffect + 0x50 => PlayerListHeaderFooter + 0x51 => CollectItem + 0x52 => EntityTeleport_f64 + 0x53 => Advancements + 0x54 => EntityProperties + 0x55 => EntityEffect + 0x56 => DeclareRecipes + 0x57 => TagsWithEntities + 0x58 => UpdateLight + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v19w02a.rs b/protocol/src/protocol/versions/v19w02a.rs new file mode 100644 index 0000000..9a2af5c --- /dev/null +++ b/protocol/src/protocol/versions/v19w02a.rs @@ -0,0 +1,174 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => TabComplete + 0x06 => ConfirmTransactionServerbound + 0x07 => EnchantItem + 0x08 => ClickWindow + 0x09 => CloseWindow + 0x0a => PluginMessageServerbound + 0x0b => EditBook + 0x0c => QueryEntityNBT + 0x0d => UseEntity + 0x0e => KeepAliveServerbound_i64 + 0x0f => Player + 0x10 => PlayerPosition + 0x11 => PlayerPositionLook + 0x12 => PlayerLook + 0x13 => VehicleMove + 0x14 => SteerBoat + 0x15 => PickItem + 0x16 => CraftRecipeRequest + 0x17 => ClientAbilities + 0x18 => PlayerDigging + 0x19 => PlayerAction + 0x1a => SteerVehicle + 0x1b => CraftingBookData + 0x1c => NameItem + 0x1d => ResourcePackStatus + 0x1e => AdvancementTab + 0x1f => SelectTrade + 0x20 => SetBeaconEffect + 0x21 => HeldItemChange + 0x22 => UpdateCommandBlock + 0x23 => UpdateCommandBlockMinecart + 0x24 => CreativeInventoryAction + 0x25 => UpdateStructureBlock + 0x26 => SetSign + 0x27 => ArmSwing + 0x28 => SpectateTeleport + 0x29 => PlayerBlockPlacement_f32 + 0x2a => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_String + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowOpenHorse + 0x15 => WindowItems + 0x16 => WindowProperty + 0x17 => WindowSetSlot + 0x18 => SetCooldown + 0x19 => PluginMessageClientbound + 0x1a => NamedSoundEffect + 0x1b => Disconnect + 0x1c => EntityAction + 0x1d => NBTQueryResponse + 0x1e => Explosion + 0x1f => ChunkUnload + 0x20 => ChangeGameState + 0x21 => KeepAliveClientbound_i64 + 0x22 => ChunkData_HeightMap + 0x23 => Effect + 0x24 => Particle_Data + 0x25 => JoinGame_i32 + 0x26 => Maps + 0x27 => Entity + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => VehicleTeleport + 0x2c => OpenBook + 0x2d => SignEditorOpen + 0x2e => CraftRecipeResponse + 0x2f => PlayerAbilities + 0x30 => CombatEvent + 0x31 => PlayerInfo + 0x32 => FacePlayer + 0x33 => TeleportPlayer_WithConfirm + 0x34 => EntityUsedBed + 0x35 => UnlockRecipes_WithSmelting + 0x36 => EntityDestroy + 0x37 => EntityRemoveEffect + 0x38 => ResourcePackSend + 0x39 => Respawn + 0x3a => EntityHeadLook + 0x3b => SelectAdvancementTab + 0x3c => WorldBorder + 0x3d => Camera + 0x3e => SetCurrentHotbarSlot + 0x3f => ScoreboardDisplay + 0x40 => EntityMetadata + 0x41 => EntityAttach + 0x42 => EntityVelocity + 0x43 => EntityEquipment + 0x44 => SetExperience + 0x45 => UpdateHealth + 0x46 => ScoreboardObjective + 0x47 => SetPassengers + 0x48 => Teams_u8 + 0x49 => UpdateScore + 0x4a => SpawnPosition + 0x4b => TimeUpdate + 0x4d => StopSound + 0x4e => SoundEffect + 0x4f => EntitySoundEffect + 0x50 => PlayerListHeaderFooter + 0x51 => CollectItem + 0x52 => EntityTeleport_f64 + 0x53 => Advancements + 0x54 => EntityProperties + 0x55 => EntityEffect + 0x56 => DeclareRecipes + 0x57 => TagsWithEntities + 0x58 => UpdateLight + 0x59 => WindowOpen_VarInt + 0x5a => TradeList_WithoutRestock // TODO: without 1.14 added fields + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_10_2.rs b/protocol/src/protocol/versions/v1_10_2.rs new file mode 100644 index 0000000..02ddc6c --- /dev/null +++ b/protocol/src/protocol/versions/v1_10_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_u8 + 0x1d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_u8 + 0x04 => SpawnPainting_String + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 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_VarIntArray + 0x23 => JoinGame_i32 + 0x24 => Maps + 0x25 => EntityMove_i16 + 0x26 => EntityLookAndMove_i16 + 0x27 => EntityLook_VarInt + 0x28 => Entity + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => PlayerAbilities + 0x2c => CombatEvent + 0x2d => PlayerInfo + 0x2e => TeleportPlayer_WithConfirm + 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_u8 + 0x42 => UpdateScore + 0x43 => SpawnPosition + 0x44 => TimeUpdate + 0x45 => Title_notext + 0x46 => SoundEffect + 0x47 => PlayerListHeaderFooter + 0x48 => CollectItem_nocount + 0x49 => EntityTeleport_f64 + 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_11_2.rs b/protocol/src/protocol/versions/v1_11_2.rs new file mode 100644 index 0000000..aa82c97 --- /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_f32 + 0x1d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_String + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 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_VarIntArray + 0x23 => JoinGame_i32 + 0x24 => Maps + 0x25 => EntityMove_i16 + 0x26 => EntityLookAndMove_i16 + 0x27 => EntityLook_VarInt + 0x28 => Entity + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => PlayerAbilities + 0x2c => CombatEvent + 0x2d => PlayerInfo + 0x2e => TeleportPlayer_WithConfirm + 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_u8 + 0x42 => UpdateScore + 0x43 => SpawnPosition + 0x44 => TimeUpdate + 0x45 => Title + 0x46 => SoundEffect + 0x47 => PlayerListHeaderFooter + 0x48 => CollectItem + 0x49 => EntityTeleport_f64 + 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..1e3b909 --- /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_f32 + 0x20 => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 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_VarIntArray + 0x23 => JoinGame_i32 + 0x24 => Maps + 0x25 => Entity + 0x26 => EntityMove_i16 + 0x27 => EntityLookAndMove_i16 + 0x28 => EntityLook_VarInt + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => CraftRecipeResponse + 0x2c => PlayerAbilities + 0x2d => CombatEvent + 0x2e => PlayerInfo + 0x2f => TeleportPlayer_WithConfirm + 0x30 => EntityUsedBed + 0x31 => UnlockRecipes_NoSmelting + 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_u8 + 0x45 => UpdateScore + 0x46 => SpawnPosition + 0x47 => TimeUpdate + 0x48 => Title + 0x49 => SoundEffect + 0x4a => PlayerListHeaderFooter + 0x4b => CollectItem + 0x4c => EntityTeleport_f64 + 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 + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_13_2.rs b/protocol/src/protocol/versions/v1_13_2.rs new file mode 100644 index 0000000..04871b1 --- /dev/null +++ b/protocol/src/protocol/versions/v1_13_2.rs @@ -0,0 +1,169 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => ChatMessage + 0x03 => ClientStatus + 0x04 => ClientSettings + 0x05 => TabComplete + 0x06 => ConfirmTransactionServerbound + 0x07 => EnchantItem + 0x08 => ClickWindow + 0x09 => CloseWindow + 0x0a => PluginMessageServerbound + 0x0b => EditBook + 0x0c => QueryEntityNBT + 0x0d => UseEntity + 0x0e => KeepAliveServerbound_i64 + 0x0f => Player + 0x10 => PlayerPosition + 0x11 => PlayerPositionLook + 0x12 => PlayerLook + 0x13 => VehicleMove + 0x14 => SteerBoat + 0x15 => PickItem + 0x16 => CraftRecipeRequest + 0x17 => ClientAbilities + 0x18 => PlayerDigging + 0x19 => PlayerAction + 0x1a => SteerVehicle + 0x1b => CraftingBookData + 0x1c => NameItem + 0x1d => ResourcePackStatus + 0x1e => AdvancementTab + 0x1f => SelectTrade + 0x20 => SetBeaconEffect + 0x21 => HeldItemChange + 0x22 => UpdateCommandBlock + 0x23 => UpdateCommandBlockMinecart + 0x24 => CreativeInventoryAction + 0x25 => UpdateStructureBlock + 0x26 => SetSign + 0x27 => ArmSwing + 0x28 => SpectateTeleport + 0x29 => PlayerBlockPlacement_f32 + 0x2a => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowOpen + 0x15 => WindowItems + 0x16 => WindowProperty + 0x17 => WindowSetSlot + 0x18 => SetCooldown + 0x19 => PluginMessageClientbound + 0x1a => NamedSoundEffect + 0x1b => Disconnect + 0x1c => EntityAction + 0x1d => NBTQueryResponse + 0x1e => Explosion + 0x1f => ChunkUnload + 0x20 => ChangeGameState + 0x21 => KeepAliveClientbound_i64 + 0x22 => ChunkData + 0x23 => Effect + 0x24 => Particle_Data + 0x25 => JoinGame_i32 + 0x26 => Maps + 0x27 => Entity + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => VehicleTeleport + 0x2c => SignEditorOpen + 0x2d => CraftRecipeResponse + 0x2e => PlayerAbilities + 0x2f => CombatEvent + 0x30 => PlayerInfo + 0x31 => FacePlayer + 0x32 => TeleportPlayer_WithConfirm + 0x33 => EntityUsedBed + 0x34 => UnlockRecipes_WithSmelting + 0x35 => EntityDestroy + 0x36 => EntityRemoveEffect + 0x37 => ResourcePackSend + 0x38 => Respawn + 0x39 => EntityHeadLook + 0x3a => SelectAdvancementTab + 0x3b => WorldBorder + 0x3c => Camera + 0x3d => SetCurrentHotbarSlot + 0x3e => ScoreboardDisplay + 0x3f => EntityMetadata + 0x40 => EntityAttach + 0x41 => EntityVelocity + 0x42 => EntityEquipment + 0x43 => SetExperience + 0x44 => UpdateHealth + 0x45 => ScoreboardObjective + 0x46 => SetPassengers + 0x47 => Teams_VarInt + 0x48 => UpdateScore + 0x49 => SpawnPosition + 0x4a => TimeUpdate + 0x4c => StopSound + 0x4d => SoundEffect + 0x4e => PlayerListHeaderFooter + 0x4f => CollectItem + 0x50 => EntityTeleport_f64 + 0x51 => Advancements + 0x52 => EntityProperties + 0x53 => EntityEffect + 0x54 => DeclareRecipes + 0x55 => Tags + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_14.rs b/protocol/src/protocol/versions/v1_14.rs new file mode 100644 index 0000000..b5c9a9a --- /dev/null +++ b/protocol/src/protocol/versions/v1_14.rs @@ -0,0 +1,179 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity + 0x0f => KeepAliveServerbound_i64 + 0x10 => LockDifficulty + 0x11 => PlayerPosition + 0x12 => PlayerPositionLook + 0x13 => PlayerLook + 0x14 => Player + 0x15 => VehicleMove + 0x16 => SteerBoat + 0x17 => PickItem + 0x18 => CraftRecipeRequest + 0x19 => ClientAbilities + 0x1a => PlayerDigging + 0x1b => PlayerAction + 0x1c => SteerVehicle + 0x1d => CraftingBookData + 0x1e => NameItem + 0x1f => ResourcePackStatus + 0x20 => AdvancementTab + 0x21 => SelectTrade + 0x22 => SetBeaconEffect + 0x23 => HeldItemChange + 0x24 => UpdateCommandBlock + 0x25 => UpdateCommandBlockMinecart + 0x26 => CreativeInventoryAction + 0x27 => UpdateJigsawBlock + 0x28 => UpdateStructureBlock + 0x29 => SetSign + 0x2a => ArmSwing + 0x2b => SpectateTeleport + 0x2c => PlayerBlockPlacement_f32 + 0x2d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty_Locked + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => WindowOpenHorse + 0x20 => KeepAliveClientbound_i64 + 0x21 => ChunkData_HeightMap + 0x22 => Effect + 0x23 => Particle_Data + 0x24 => UpdateLight + 0x25 => JoinGame_i32_ViewDistance + 0x26 => Maps + 0x27 => TradeList_WithoutRestock + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => Entity + 0x2c => VehicleTeleport + 0x2d => OpenBook + 0x2e => WindowOpen_VarInt + 0x2f => SignEditorOpen + 0x30 => CraftRecipeResponse + 0x31 => PlayerAbilities + 0x32 => CombatEvent + 0x33 => PlayerInfo + 0x34 => FacePlayer + 0x35 => TeleportPlayer_WithConfirm + 0x36 => UnlockRecipes_WithSmelting + 0x37 => EntityDestroy + 0x38 => EntityRemoveEffect + 0x39 => ResourcePackSend + 0x3a => Respawn + 0x3b => EntityHeadLook + 0x3c => SelectAdvancementTab + 0x3d => WorldBorder + 0x3e => Camera + 0x3f => SetCurrentHotbarSlot + 0x40 => UpdateViewPosition + 0x41 => UpdateViewDistance + 0x42 => ScoreboardDisplay + 0x43 => EntityMetadata + 0x44 => EntityAttach + 0x45 => EntityVelocity + 0x46 => EntityEquipment + 0x47 => SetExperience + 0x48 => UpdateHealth + 0x49 => ScoreboardObjective + 0x4a => SetPassengers + 0x4b => Teams_VarInt + 0x4c => UpdateScore + 0x4d => SpawnPosition + 0x4e => TimeUpdate + 0x4f => Title + 0x50 => EntitySoundEffect + 0x51 => SoundEffect + 0x52 => StopSound + 0x53 => PlayerListHeaderFooter + 0x54 => NBTQueryResponse + 0x55 => CollectItem + 0x56 => EntityTeleport_f64 + 0x57 => Advancements + 0x58 => EntityProperties + 0x59 => EntityEffect + 0x5a => DeclareRecipes + 0x5b => TagsWithEntities + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_14_1.rs b/protocol/src/protocol/versions/v1_14_1.rs new file mode 100644 index 0000000..b5c9a9a --- /dev/null +++ b/protocol/src/protocol/versions/v1_14_1.rs @@ -0,0 +1,179 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity + 0x0f => KeepAliveServerbound_i64 + 0x10 => LockDifficulty + 0x11 => PlayerPosition + 0x12 => PlayerPositionLook + 0x13 => PlayerLook + 0x14 => Player + 0x15 => VehicleMove + 0x16 => SteerBoat + 0x17 => PickItem + 0x18 => CraftRecipeRequest + 0x19 => ClientAbilities + 0x1a => PlayerDigging + 0x1b => PlayerAction + 0x1c => SteerVehicle + 0x1d => CraftingBookData + 0x1e => NameItem + 0x1f => ResourcePackStatus + 0x20 => AdvancementTab + 0x21 => SelectTrade + 0x22 => SetBeaconEffect + 0x23 => HeldItemChange + 0x24 => UpdateCommandBlock + 0x25 => UpdateCommandBlockMinecart + 0x26 => CreativeInventoryAction + 0x27 => UpdateJigsawBlock + 0x28 => UpdateStructureBlock + 0x29 => SetSign + 0x2a => ArmSwing + 0x2b => SpectateTeleport + 0x2c => PlayerBlockPlacement_f32 + 0x2d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty_Locked + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => WindowOpenHorse + 0x20 => KeepAliveClientbound_i64 + 0x21 => ChunkData_HeightMap + 0x22 => Effect + 0x23 => Particle_Data + 0x24 => UpdateLight + 0x25 => JoinGame_i32_ViewDistance + 0x26 => Maps + 0x27 => TradeList_WithoutRestock + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => Entity + 0x2c => VehicleTeleport + 0x2d => OpenBook + 0x2e => WindowOpen_VarInt + 0x2f => SignEditorOpen + 0x30 => CraftRecipeResponse + 0x31 => PlayerAbilities + 0x32 => CombatEvent + 0x33 => PlayerInfo + 0x34 => FacePlayer + 0x35 => TeleportPlayer_WithConfirm + 0x36 => UnlockRecipes_WithSmelting + 0x37 => EntityDestroy + 0x38 => EntityRemoveEffect + 0x39 => ResourcePackSend + 0x3a => Respawn + 0x3b => EntityHeadLook + 0x3c => SelectAdvancementTab + 0x3d => WorldBorder + 0x3e => Camera + 0x3f => SetCurrentHotbarSlot + 0x40 => UpdateViewPosition + 0x41 => UpdateViewDistance + 0x42 => ScoreboardDisplay + 0x43 => EntityMetadata + 0x44 => EntityAttach + 0x45 => EntityVelocity + 0x46 => EntityEquipment + 0x47 => SetExperience + 0x48 => UpdateHealth + 0x49 => ScoreboardObjective + 0x4a => SetPassengers + 0x4b => Teams_VarInt + 0x4c => UpdateScore + 0x4d => SpawnPosition + 0x4e => TimeUpdate + 0x4f => Title + 0x50 => EntitySoundEffect + 0x51 => SoundEffect + 0x52 => StopSound + 0x53 => PlayerListHeaderFooter + 0x54 => NBTQueryResponse + 0x55 => CollectItem + 0x56 => EntityTeleport_f64 + 0x57 => Advancements + 0x58 => EntityProperties + 0x59 => EntityEffect + 0x5a => DeclareRecipes + 0x5b => TagsWithEntities + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_14_2.rs b/protocol/src/protocol/versions/v1_14_2.rs new file mode 100644 index 0000000..b5c9a9a --- /dev/null +++ b/protocol/src/protocol/versions/v1_14_2.rs @@ -0,0 +1,179 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity + 0x0f => KeepAliveServerbound_i64 + 0x10 => LockDifficulty + 0x11 => PlayerPosition + 0x12 => PlayerPositionLook + 0x13 => PlayerLook + 0x14 => Player + 0x15 => VehicleMove + 0x16 => SteerBoat + 0x17 => PickItem + 0x18 => CraftRecipeRequest + 0x19 => ClientAbilities + 0x1a => PlayerDigging + 0x1b => PlayerAction + 0x1c => SteerVehicle + 0x1d => CraftingBookData + 0x1e => NameItem + 0x1f => ResourcePackStatus + 0x20 => AdvancementTab + 0x21 => SelectTrade + 0x22 => SetBeaconEffect + 0x23 => HeldItemChange + 0x24 => UpdateCommandBlock + 0x25 => UpdateCommandBlockMinecart + 0x26 => CreativeInventoryAction + 0x27 => UpdateJigsawBlock + 0x28 => UpdateStructureBlock + 0x29 => SetSign + 0x2a => ArmSwing + 0x2b => SpectateTeleport + 0x2c => PlayerBlockPlacement_f32 + 0x2d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty_Locked + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => WindowOpenHorse + 0x20 => KeepAliveClientbound_i64 + 0x21 => ChunkData_HeightMap + 0x22 => Effect + 0x23 => Particle_Data + 0x24 => UpdateLight + 0x25 => JoinGame_i32_ViewDistance + 0x26 => Maps + 0x27 => TradeList_WithoutRestock + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => Entity + 0x2c => VehicleTeleport + 0x2d => OpenBook + 0x2e => WindowOpen_VarInt + 0x2f => SignEditorOpen + 0x30 => CraftRecipeResponse + 0x31 => PlayerAbilities + 0x32 => CombatEvent + 0x33 => PlayerInfo + 0x34 => FacePlayer + 0x35 => TeleportPlayer_WithConfirm + 0x36 => UnlockRecipes_WithSmelting + 0x37 => EntityDestroy + 0x38 => EntityRemoveEffect + 0x39 => ResourcePackSend + 0x3a => Respawn + 0x3b => EntityHeadLook + 0x3c => SelectAdvancementTab + 0x3d => WorldBorder + 0x3e => Camera + 0x3f => SetCurrentHotbarSlot + 0x40 => UpdateViewPosition + 0x41 => UpdateViewDistance + 0x42 => ScoreboardDisplay + 0x43 => EntityMetadata + 0x44 => EntityAttach + 0x45 => EntityVelocity + 0x46 => EntityEquipment + 0x47 => SetExperience + 0x48 => UpdateHealth + 0x49 => ScoreboardObjective + 0x4a => SetPassengers + 0x4b => Teams_VarInt + 0x4c => UpdateScore + 0x4d => SpawnPosition + 0x4e => TimeUpdate + 0x4f => Title + 0x50 => EntitySoundEffect + 0x51 => SoundEffect + 0x52 => StopSound + 0x53 => PlayerListHeaderFooter + 0x54 => NBTQueryResponse + 0x55 => CollectItem + 0x56 => EntityTeleport_f64 + 0x57 => Advancements + 0x58 => EntityProperties + 0x59 => EntityEffect + 0x5a => DeclareRecipes + 0x5b => TagsWithEntities + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_14_3.rs b/protocol/src/protocol/versions/v1_14_3.rs new file mode 100644 index 0000000..a5eebf2 --- /dev/null +++ b/protocol/src/protocol/versions/v1_14_3.rs @@ -0,0 +1,179 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity + 0x0f => KeepAliveServerbound_i64 + 0x10 => LockDifficulty + 0x11 => PlayerPosition + 0x12 => PlayerPositionLook + 0x13 => PlayerLook + 0x14 => Player + 0x15 => VehicleMove + 0x16 => SteerBoat + 0x17 => PickItem + 0x18 => CraftRecipeRequest + 0x19 => ClientAbilities + 0x1a => PlayerDigging + 0x1b => PlayerAction + 0x1c => SteerVehicle + 0x1d => CraftingBookData + 0x1e => NameItem + 0x1f => ResourcePackStatus + 0x20 => AdvancementTab + 0x21 => SelectTrade + 0x22 => SetBeaconEffect + 0x23 => HeldItemChange + 0x24 => UpdateCommandBlock + 0x25 => UpdateCommandBlockMinecart + 0x26 => CreativeInventoryAction + 0x27 => UpdateJigsawBlock + 0x28 => UpdateStructureBlock + 0x29 => SetSign + 0x2a => ArmSwing + 0x2b => SpectateTeleport + 0x2c => PlayerBlockPlacement_f32 + 0x2d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty_Locked + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => WindowOpenHorse + 0x20 => KeepAliveClientbound_i64 + 0x21 => ChunkData_HeightMap + 0x22 => Effect + 0x23 => Particle_Data + 0x24 => UpdateLight + 0x25 => JoinGame_i32_ViewDistance + 0x26 => Maps + 0x27 => TradeList_WithRestock + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => Entity + 0x2c => VehicleTeleport + 0x2d => OpenBook + 0x2e => WindowOpen_VarInt + 0x2f => SignEditorOpen + 0x30 => CraftRecipeResponse + 0x31 => PlayerAbilities + 0x32 => CombatEvent + 0x33 => PlayerInfo + 0x34 => FacePlayer + 0x35 => TeleportPlayer_WithConfirm + 0x36 => UnlockRecipes_WithSmelting + 0x37 => EntityDestroy + 0x38 => EntityRemoveEffect + 0x39 => ResourcePackSend + 0x3a => Respawn + 0x3b => EntityHeadLook + 0x3c => SelectAdvancementTab + 0x3d => WorldBorder + 0x3e => Camera + 0x3f => SetCurrentHotbarSlot + 0x40 => UpdateViewPosition + 0x41 => UpdateViewDistance + 0x42 => ScoreboardDisplay + 0x43 => EntityMetadata + 0x44 => EntityAttach + 0x45 => EntityVelocity + 0x46 => EntityEquipment + 0x47 => SetExperience + 0x48 => UpdateHealth + 0x49 => ScoreboardObjective + 0x4a => SetPassengers + 0x4b => Teams_VarInt + 0x4c => UpdateScore + 0x4d => SpawnPosition + 0x4e => TimeUpdate + 0x4f => Title + 0x50 => EntitySoundEffect + 0x51 => SoundEffect + 0x52 => StopSound + 0x53 => PlayerListHeaderFooter + 0x54 => NBTQueryResponse + 0x55 => CollectItem + 0x56 => EntityTeleport_f64 + 0x57 => Advancements + 0x58 => EntityProperties + 0x59 => EntityEffect + 0x5a => DeclareRecipes + 0x5b => TagsWithEntities + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_14_4.rs b/protocol/src/protocol/versions/v1_14_4.rs new file mode 100644 index 0000000..60be91a --- /dev/null +++ b/protocol/src/protocol/versions/v1_14_4.rs @@ -0,0 +1,180 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity + 0x0f => KeepAliveServerbound_i64 + 0x10 => LockDifficulty + 0x11 => PlayerPosition + 0x12 => PlayerPositionLook + 0x13 => PlayerLook + 0x14 => Player + 0x15 => VehicleMove + 0x16 => SteerBoat + 0x17 => PickItem + 0x18 => CraftRecipeRequest + 0x19 => ClientAbilities + 0x1a => PlayerDigging + 0x1b => PlayerAction + 0x1c => SteerVehicle + 0x1d => CraftingBookData + 0x1e => NameItem + 0x1f => ResourcePackStatus + 0x20 => AdvancementTab + 0x21 => SelectTrade + 0x22 => SetBeaconEffect + 0x23 => HeldItemChange + 0x24 => UpdateCommandBlock + 0x25 => UpdateCommandBlockMinecart + 0x26 => CreativeInventoryAction + 0x27 => UpdateJigsawBlock + 0x28 => UpdateStructureBlock + 0x29 => SetSign + 0x2a => ArmSwing + 0x2b => SpectateTeleport + 0x2c => PlayerBlockPlacement_f32 + 0x2d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_WithMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty_Locked + 0x0e => ServerMessage + 0x0f => MultiBlockChange_VarInt + 0x10 => TabCompleteReply + 0x11 => DeclareCommands + 0x12 => ConfirmTransaction + 0x13 => WindowClose + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => WindowOpenHorse + 0x20 => KeepAliveClientbound_i64 + 0x21 => ChunkData_HeightMap + 0x22 => Effect + 0x23 => Particle_Data + 0x24 => UpdateLight + 0x25 => JoinGame_i32_ViewDistance + 0x26 => Maps + 0x27 => TradeList_WithRestock + 0x28 => EntityMove_i16 + 0x29 => EntityLookAndMove_i16 + 0x2a => EntityLook_VarInt + 0x2b => Entity + 0x2c => VehicleTeleport + 0x2d => OpenBook + 0x2e => WindowOpen_VarInt + 0x2f => SignEditorOpen + 0x30 => CraftRecipeResponse + 0x31 => PlayerAbilities + 0x32 => CombatEvent + 0x33 => PlayerInfo + 0x34 => FacePlayer + 0x35 => TeleportPlayer_WithConfirm + 0x36 => UnlockRecipes_WithSmelting + 0x37 => EntityDestroy + 0x38 => EntityRemoveEffect + 0x39 => ResourcePackSend + 0x3a => Respawn + 0x3b => EntityHeadLook + 0x3c => SelectAdvancementTab + 0x3d => WorldBorder + 0x3e => Camera + 0x3f => SetCurrentHotbarSlot + 0x40 => UpdateViewPosition + 0x41 => UpdateViewDistance + 0x42 => ScoreboardDisplay + 0x43 => EntityMetadata + 0x44 => EntityAttach + 0x45 => EntityVelocity + 0x46 => EntityEquipment + 0x47 => SetExperience + 0x48 => UpdateHealth + 0x49 => ScoreboardObjective + 0x4a => SetPassengers + 0x4b => Teams_VarInt + 0x4c => UpdateScore + 0x4d => SpawnPosition + 0x4e => TimeUpdate + 0x4f => Title + 0x50 => EntitySoundEffect + 0x51 => SoundEffect + 0x52 => StopSound + 0x53 => PlayerListHeaderFooter + 0x54 => NBTQueryResponse + 0x55 => CollectItem + 0x56 => EntityTeleport_f64 + 0x57 => Advancements + 0x58 => EntityProperties + 0x59 => EntityEffect + 0x5a => DeclareRecipes + 0x5b => TagsWithEntities + 0x5c => AcknowledgePlayerDigging + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_15_1.rs b/protocol/src/protocol/versions/v1_15_1.rs new file mode 100644 index 0000000..6c3386c --- /dev/null +++ b/protocol/src/protocol/versions/v1_15_1.rs @@ -0,0 +1,180 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + 0x03 => ChatMessage + 0x04 => ClientStatus + 0x05 => ClientSettings + 0x06 => TabComplete + 0x07 => ConfirmTransactionServerbound + 0x08 => ClickWindowButton + 0x09 => ClickWindow + 0x0a => CloseWindow + 0x0b => PluginMessageServerbound + 0x0c => EditBook + 0x0d => QueryEntityNBT + 0x0e => UseEntity + 0x0f => KeepAliveServerbound_i64 + 0x10 => LockDifficulty + 0x11 => PlayerPosition + 0x12 => PlayerPositionLook + 0x13 => PlayerLook + 0x14 => Player + 0x15 => VehicleMove + 0x16 => SteerBoat + 0x17 => PickItem + 0x18 => CraftRecipeRequest + 0x19 => ClientAbilities + 0x1a => PlayerDigging + 0x1b => PlayerAction + 0x1c => SteerVehicle + 0x1d => CraftingBookData + 0x1e => NameItem + 0x1f => ResourcePackStatus + 0x20 => AdvancementTab + 0x21 => SelectTrade + 0x22 => SetBeaconEffect + 0x23 => HeldItemChange + 0x24 => UpdateCommandBlock + 0x25 => UpdateCommandBlockMinecart + 0x26 => CreativeInventoryAction + 0x27 => UpdateJigsawBlock + 0x28 => UpdateStructureBlock + 0x29 => SetSign + 0x2a => ArmSwing + 0x2b => SpectateTeleport + 0x2c => PlayerBlockPlacement_f32 + 0x2d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_NoMeta + 0x04 => SpawnPainting_VarInt + 0x05 => SpawnPlayer_f64_NoMeta + 0x06 => Animation + 0x07 => Statistics + 0x08 => AcknowledgePlayerDigging + 0x09 => BlockBreakAnimation + 0x0a => UpdateBlockEntity + 0x0b => BlockAction + 0x0c => BlockChange_VarInt + 0x0d => BossBar + 0x0e => ServerDifficulty_Locked + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 0x11 => TabCompleteReply + 0x12 => DeclareCommands + 0x13 => ConfirmTransaction + 0x14 => WindowClose + 0x15 => WindowItems + 0x16 => WindowProperty + 0x17 => WindowSetSlot + 0x18 => SetCooldown + 0x19 => PluginMessageClientbound + 0x1a => NamedSoundEffect + 0x1b => Disconnect + 0x1c => EntityAction + 0x1d => Explosion + 0x1e => ChunkUnload + 0x1f => ChangeGameState + 0x20 => WindowOpenHorse + 0x21 => KeepAliveClientbound_i64 + 0x22 => ChunkData_Biomes3D + 0x23 => Effect + 0x24 => Particle_f64 + 0x25 => UpdateLight + 0x26 => JoinGame_HashedSeed_Respawn + 0x27 => Maps + 0x28 => TradeList_WithRestock + 0x29 => EntityMove_i16 + 0x2a => EntityLookAndMove_i16 + 0x2b => EntityLook_VarInt + 0x2c => Entity + 0x2d => VehicleTeleport + 0x2e => OpenBook + 0x2f => WindowOpen_VarInt + 0x30 => SignEditorOpen + 0x31 => CraftRecipeResponse + 0x32 => PlayerAbilities + 0x33 => CombatEvent + 0x34 => PlayerInfo + 0x35 => FacePlayer + 0x36 => TeleportPlayer_WithConfirm + 0x37 => UnlockRecipes_WithSmelting + 0x38 => EntityDestroy + 0x39 => EntityRemoveEffect + 0x3a => ResourcePackSend + 0x3b => Respawn_HashedSeed + 0x3c => EntityHeadLook + 0x3d => SelectAdvancementTab + 0x3e => WorldBorder + 0x3f => Camera + 0x40 => SetCurrentHotbarSlot + 0x41 => UpdateViewPosition + 0x42 => UpdateViewDistance + 0x43 => ScoreboardDisplay + 0x44 => EntityMetadata + 0x45 => EntityAttach + 0x46 => EntityVelocity + 0x47 => EntityEquipment + 0x48 => SetExperience + 0x49 => UpdateHealth + 0x4a => ScoreboardObjective + 0x4b => SetPassengers + 0x4c => Teams_VarInt + 0x4d => UpdateScore + 0x4e => SpawnPosition + 0x4f => TimeUpdate + 0x50 => Title + 0x51 => EntitySoundEffect + 0x52 => SoundEffect + 0x53 => StopSound + 0x54 => PlayerListHeaderFooter + 0x55 => NBTQueryResponse + 0x56 => CollectItem + 0x57 => EntityTeleport_f64 + 0x58 => Advancements + 0x59 => EntityProperties + 0x5a => EntityEffect + 0x5b => DeclareRecipes + 0x5c => TagsWithEntities + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_7_10.rs b/protocol/src/protocol/versions/v1_7_10.rs new file mode 100644 index 0000000..c930e42 --- /dev/null +++ b/protocol/src/protocol/versions/v1_7_10.rs @@ -0,0 +1,128 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => KeepAliveServerbound_i32 + 0x01 => ChatMessage + 0x02 => UseEntity_Handsfree_i32 + 0x03 => Player + 0x04 => PlayerPosition_HeadY + 0x05 => PlayerLook + 0x06 => PlayerPositionLook_HeadY + 0x07 => PlayerDigging_u8_u8y + 0x08 => PlayerBlockPlacement_u8_Item_u8y + 0x09 => HeldItemChange + 0x0a => ArmSwing_Handsfree_ID + 0x0b => PlayerAction_i32 + 0x0c => SteerVehicle_jump_unmount + 0x0d => CloseWindow + 0x0e => ClickWindow_u8 + 0x0f => ConfirmTransactionServerbound + 0x10 => CreativeInventoryAction + 0x11 => EnchantItem + 0x12 => SetSign_i16y + 0x13 => ClientAbilities + 0x14 => TabComplete_NoAssume_NoTarget + 0x15 => ClientSettings_u8_Handsfree_Difficulty + 0x16 => ClientStatus_u8 + 0x17 => PluginMessageServerbound_i16 + } + clientbound Clientbound { + 0x00 => KeepAliveClientbound_i32 + 0x01 => JoinGame_i8_NoDebug + 0x02 => ServerMessage_NoPosition + 0x03 => TimeUpdate + 0x04 => EntityEquipment_u16_i32 + 0x05 => SpawnPosition_i32 + 0x06 => UpdateHealth_u16 + 0x07 => Respawn + 0x08 => TeleportPlayer_OnGround + 0x09 => SetCurrentHotbarSlot + 0x0a => EntityUsedBed_i32 + 0x0b => Animation + 0x0c => SpawnPlayer_i32_HeldItem_String + 0x0d => CollectItem_nocount_i32 + 0x0e => SpawnObject_i32_NoUUID + 0x0f => SpawnMob_u8_i32_NoUUID + 0x10 => SpawnPainting_NoUUID_i32 + 0x11 => SpawnExperienceOrb_i32 + 0x12 => EntityVelocity_i32 + 0x13 => EntityDestroy_u8 + 0x14 => Entity_i32 + 0x15 => EntityMove_i8_i32_NoGround + 0x16 => EntityLook_i32_NoGround + 0x17 => EntityLookAndMove_i8_i32_NoGround + 0x18 => EntityTeleport_i32_i32_NoGround + 0x19 => EntityHeadLook_i32 + 0x1a => EntityStatus + 0x1b => EntityAttach_leashed + 0x1c => EntityMetadata_i32 + 0x1d => EntityEffect_i32 + 0x1e => EntityRemoveEffect_i32 + 0x1f => SetExperience_i16 + 0x20 => EntityProperties_i32 + 0x21 => ChunkData_17 + 0x22 => MultiBlockChange_u16 + 0x23 => BlockChange_u8 + 0x24 => BlockAction_u16 + 0x25 => BlockBreakAnimation_i32 + 0x26 => ChunkDataBulk_17 + 0x27 => Explosion + 0x28 => Effect_u8y + 0x29 => NamedSoundEffect_u8_NoCategory + 0x2a => Particle_Named + 0x2b => ChangeGameState + 0x2c => SpawnGlobalEntity_i32 + 0x2d => WindowOpen_u8 + 0x2e => WindowClose + 0x2f => WindowSetSlot + 0x30 => WindowItems + 0x31 => WindowProperty + 0x32 => ConfirmTransaction + 0x33 => UpdateSign_u16 + 0x34 => Maps_NoTracking_Data + 0x35 => UpdateBlockEntity_Data + 0x36 => SignEditorOpen_i32 + 0x37 => Statistics + 0x38 => PlayerInfo_String + 0x39 => PlayerAbilities + 0x3a => TabCompleteReply + 0x3b => ScoreboardObjective_NoMode + 0x3c => UpdateScore_i32 + 0x3d => ScoreboardDisplay + 0x3e => Teams_NoVisColor + 0x3f => PluginMessageClientbound_i16 + 0x40 => Disconnect + -0x1a => CoFHLib_SendUUID + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse_i16 + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest_i16 + 0x02 => LoginSuccess + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/protocol/src/protocol/versions/v1_8_9.rs b/protocol/src/protocol/versions/v1_8_9.rs new file mode 100644 index 0000000..c645c45 --- /dev/null +++ b/protocol/src/protocol/versions/v1_8_9.rs @@ -0,0 +1,139 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => KeepAliveServerbound_VarInt + 0x01 => ChatMessage + 0x02 => UseEntity_Handsfree + 0x03 => Player + 0x04 => PlayerPosition + 0x05 => PlayerLook + 0x06 => PlayerPositionLook + 0x07 => PlayerDigging_u8 + 0x08 => PlayerBlockPlacement_u8_Item + 0x09 => HeldItemChange + 0x0a => ArmSwing_Handsfree + 0x0b => PlayerAction + 0x0c => SteerVehicle + 0x0d => CloseWindow + 0x0e => ClickWindow_u8 + 0x0f => ConfirmTransactionServerbound + 0x10 => CreativeInventoryAction + 0x11 => EnchantItem + 0x12 => SetSign + 0x13 => ClientAbilities + 0x14 => TabComplete_NoAssume + 0x15 => ClientSettings_u8_Handsfree + 0x16 => ClientStatus + 0x17 => PluginMessageServerbound + 0x18 => SpectateTeleport + 0x19 => ResourcePackStatus + } + clientbound Clientbound { + 0x00 => KeepAliveClientbound_VarInt + 0x01 => JoinGame_i8 + 0x02 => ServerMessage + 0x03 => TimeUpdate + 0x04 => EntityEquipment_u16 + 0x05 => SpawnPosition + 0x06 => UpdateHealth + 0x07 => Respawn + 0x08 => TeleportPlayer_NoConfirm + 0x09 => SetCurrentHotbarSlot + 0x0a => EntityUsedBed + 0x0b => Animation + 0x0c => SpawnPlayer_i32_HeldItem + 0x0d => CollectItem_nocount + 0x0e => SpawnObject_i32_NoUUID + 0x0f => SpawnMob_u8_i32_NoUUID + 0x10 => SpawnPainting_NoUUID + 0x11 => SpawnExperienceOrb_i32 + 0x12 => EntityVelocity + 0x13 => EntityDestroy + 0x14 => Entity + 0x15 => EntityMove_i8 + 0x16 => EntityLook_VarInt + 0x17 => EntityLookAndMove_i8 + 0x18 => EntityTeleport_i32 + 0x19 => EntityHeadLook + 0x1a => EntityStatus + 0x1b => EntityAttach_leashed + 0x1c => EntityMetadata + 0x1d => EntityEffect + 0x1e => EntityRemoveEffect + 0x1f => SetExperience + 0x20 => EntityProperties + 0x21 => ChunkData_NoEntities_u16 + 0x22 => MultiBlockChange_VarInt + 0x23 => BlockChange_VarInt + 0x24 => BlockAction + 0x25 => BlockBreakAnimation + 0x26 => ChunkDataBulk + 0x27 => Explosion + 0x28 => Effect + 0x29 => NamedSoundEffect_u8_NoCategory + 0x2a => Particle_VarIntArray + 0x2b => ChangeGameState + 0x2c => SpawnGlobalEntity_i32 + 0x2d => WindowOpen + 0x2e => WindowClose + 0x2f => WindowSetSlot + 0x30 => WindowItems + 0x31 => WindowProperty + 0x32 => ConfirmTransaction + 0x33 => UpdateSign + 0x34 => Maps_NoTracking + 0x35 => UpdateBlockEntity + 0x36 => SignEditorOpen + 0x37 => Statistics + 0x38 => PlayerInfo + 0x39 => PlayerAbilities + 0x3a => TabCompleteReply + 0x3b => ScoreboardObjective + 0x3c => UpdateScore + 0x3d => ScoreboardDisplay + 0x3e => Teams_u8 + 0x3f => PluginMessageClientbound + 0x40 => Disconnect + 0x41 => ServerDifficulty + 0x42 => CombatEvent + 0x43 => Camera + 0x44 => WorldBorder + 0x45 => Title_notext_component + 0x46 => SetCompression + 0x47 => PlayerListHeaderFooter + 0x48 => ResourcePackSend + 0x49 => EntityUpdateNBT + } + } + 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_9.rs b/protocol/src/protocol/versions/v1_9.rs new file mode 100644 index 0000000..7b714e8 --- /dev/null +++ b/protocol/src/protocol/versions/v1_9.rs @@ -0,0 +1,146 @@ +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_hash + 0x17 => HeldItemChange + 0x18 => CreativeInventoryAction + 0x19 => SetSign + 0x1a => ArmSwing + 0x1b => SpectateTeleport + 0x1c => PlayerBlockPlacement_u8 + 0x1d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_u8 + 0x04 => SpawnPainting_String + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 0x11 => ConfirmTransaction + 0x12 => WindowClose + 0x13 => WindowOpen + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect_u8 + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => KeepAliveClientbound_VarInt + 0x20 => ChunkData_NoEntities + 0x21 => Effect + 0x22 => Particle_VarIntArray + 0x23 => JoinGame_i8 + 0x24 => Maps + 0x25 => EntityMove_i16 + 0x26 => EntityLookAndMove_i16 + 0x27 => EntityLook_VarInt + 0x28 => Entity + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => PlayerAbilities + 0x2c => CombatEvent + 0x2d => PlayerInfo + 0x2e => TeleportPlayer_WithConfirm + 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_u8 + 0x42 => UpdateScore + 0x43 => SpawnPosition + 0x44 => TimeUpdate + 0x45 => Title_notext + 0x46 => UpdateSign + 0x47 => SoundEffect_u8 + 0x48 => PlayerListHeaderFooter + 0x49 => CollectItem_nocount + 0x4a => EntityTeleport_f64 + 0x4b => EntityProperties + 0x4c => 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_9_2.rs b/protocol/src/protocol/versions/v1_9_2.rs new file mode 100644 index 0000000..ecd1e28 --- /dev/null +++ b/protocol/src/protocol/versions/v1_9_2.rs @@ -0,0 +1,146 @@ +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_hash + 0x17 => HeldItemChange + 0x18 => CreativeInventoryAction + 0x19 => SetSign + 0x1a => ArmSwing + 0x1b => SpectateTeleport + 0x1c => PlayerBlockPlacement_u8 + 0x1d => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject + 0x01 => SpawnExperienceOrb + 0x02 => SpawnGlobalEntity + 0x03 => SpawnMob_u8 + 0x04 => SpawnPainting_String + 0x05 => SpawnPlayer_f64 + 0x06 => Animation + 0x07 => Statistics + 0x08 => BlockBreakAnimation + 0x09 => UpdateBlockEntity + 0x0a => BlockAction + 0x0b => BlockChange_VarInt + 0x0c => BossBar + 0x0d => ServerDifficulty + 0x0e => TabCompleteReply + 0x0f => ServerMessage + 0x10 => MultiBlockChange_VarInt + 0x11 => ConfirmTransaction + 0x12 => WindowClose + 0x13 => WindowOpen + 0x14 => WindowItems + 0x15 => WindowProperty + 0x16 => WindowSetSlot + 0x17 => SetCooldown + 0x18 => PluginMessageClientbound + 0x19 => NamedSoundEffect_u8 + 0x1a => Disconnect + 0x1b => EntityAction + 0x1c => Explosion + 0x1d => ChunkUnload + 0x1e => ChangeGameState + 0x1f => KeepAliveClientbound_VarInt + 0x20 => ChunkData_NoEntities + 0x21 => Effect + 0x22 => Particle_VarIntArray + 0x23 => JoinGame_i32 + 0x24 => Maps + 0x25 => EntityMove_i16 + 0x26 => EntityLookAndMove_i16 + 0x27 => EntityLook_VarInt + 0x28 => Entity + 0x29 => VehicleTeleport + 0x2a => SignEditorOpen + 0x2b => PlayerAbilities + 0x2c => CombatEvent + 0x2d => PlayerInfo + 0x2e => TeleportPlayer_WithConfirm + 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_u8 + 0x42 => UpdateScore + 0x43 => SpawnPosition + 0x44 => TimeUpdate + 0x45 => Title_notext + 0x46 => UpdateSign + 0x47 => SoundEffect_u8 + 0x48 => PlayerListHeaderFooter + 0x49 => CollectItem_nocount + 0x4a => EntityTeleport_f64 + 0x4b => EntityProperties + 0x4c => 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/types/bit/map.rs b/protocol/src/types/bit/map.rs new file mode 100644 index 0000000..2f86979 --- /dev/null +++ b/protocol/src/types/bit/map.rs @@ -0,0 +1,105 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub struct Map { + bits: Vec, + pub bit_size: usize, + length: usize, +} + +#[test] +fn test_map() { + let mut map = Map::new(4096, 4); + for i in 0..4096 { + for j in 0..16 { + map.set(i, j); + if map.get(i) != j { + panic!("Fail"); + } + } + } +} + +#[test] +fn test_map_odd() { + for size in 1..16 { + let mut map = Map::new(64 * 3, size); + let max = (1 << size) - 1; + for i in 0..64 * 3 { + for j in 0..max { + map.set(i, j); + if map.get(i) != j { + panic!("Index: {} wanted {} and got {}", i, j, map.get(i)); + } + } + } + } +} + +impl Map { + pub fn new(len: usize, size: usize) -> Map { + let mut map = Map { + bit_size: size, + length: len, + bits: Vec::with_capacity((len * size) / 64), + }; + for _ in 0..len { + map.bits.push(0) + } + map + } + pub fn from_raw(bits: Vec, size: usize) -> Map { + Map { + length: (bits.len()*64 + (size-1)) / size, + bit_size: size, + bits, + } + } + + pub fn resize(&self, size: usize) -> Map { + let mut n = Map::new(self.length, size); + for i in 0..self.length { + n.set(i, self.get(i)); + } + n + } + + pub fn set(&mut self, i: usize, val: usize) { + let i = i * self.bit_size; + let pos = i / 64; + let mask = (1u64 << self.bit_size) - 1; + let ii = i % 64; + self.bits[pos] = (self.bits[pos] & !(mask << ii)) | ((val as u64) << ii); + let pos2 = (i + self.bit_size - 1) / 64; + if pos2 != pos { + let used = 64 - ii; + let rem = self.bit_size - used; + self.bits[pos2] = self.bits[pos2] >> rem << rem | (val as u64 >> used); + } + } + + pub fn get(&self, i: usize) -> usize { + let i = i * self.bit_size; + let pos = i / 64; + let mask = (1 << self.bit_size) - 1; + let ii = i % 64; + let pos2 = (i + self.bit_size - 1) / 64; + if pos2 == pos { + ((self.bits[pos] >> ii) & mask) as usize + } else { + let used = 64 - ii; + (((self.bits[pos] >> ii) | (self.bits[pos2] << used)) & mask) as usize + } + } +} diff --git a/protocol/src/types/bit/mod.rs b/protocol/src/types/bit/mod.rs new file mode 100644 index 0000000..6754eab --- /dev/null +++ b/protocol/src/types/bit/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod set; +pub mod map; + +pub use self::set::Set; +pub use self::map::Map; diff --git a/protocol/src/types/bit/set.rs b/protocol/src/types/bit/set.rs new file mode 100644 index 0000000..894d936 --- /dev/null +++ b/protocol/src/types/bit/set.rs @@ -0,0 +1,74 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[derive(Clone, Debug)] +pub struct Set { + data: Vec, +} + +#[test] +fn test_set() { + let mut set = Set::new(200); + for i in 0..200 { + if i % 3 == 0 { + set.set(i, true) + } + } + for i in 0..200 { + if set.get(i) != (i % 3 == 0) { + panic!("Fail") + } + } +} + +impl Set { + pub fn new(size: usize) -> Set { + Set { data: vec![0; (size + 63) / 64] } + } + + pub fn resize(&mut self, new_size: usize) { + self.data.resize((new_size + 63) / 64, 0); + } + + pub fn capacity(&self) -> usize { + self.data.len() * 64 + } + + pub fn set(&mut self, i: usize, v: bool) { + if v { + self.data[i >> 6] |= 1 << (i & 0x3F) + } else { + self.data[i >> 6] &= !(1 << (i & 0x3F)) + } + } + + pub fn get(&self, i: usize) -> bool { + (self.data[i >> 6] & (1 << (i & 0x3F))) != 0 + } + + pub fn includes_set(&self, other: &Set) -> bool { + for (a, b) in self.data.iter().zip(&other.data) { + if a & b != *b { + return false; + } + } + true + } + + pub fn or(&mut self, other: &Set) { + for (a, b) in self.data.iter_mut().zip(&other.data) { + *a |= *b; + } + } +} diff --git a/protocol/src/types/hash.rs b/protocol/src/types/hash.rs new file mode 100644 index 0000000..cf16af9 --- /dev/null +++ b/protocol/src/types/hash.rs @@ -0,0 +1,24 @@ + + +use std::hash::Hasher; + +pub struct FNVHash(u64); + +impl Hasher for FNVHash { + fn write(&mut self, bytes: &[u8]) { + for b in bytes { + self.0 = self.0.wrapping_mul(0x100000001b3); + self.0 ^= *b as u64 + } + } + + fn finish(&self) -> u64 { + self.0 + } +} + +impl Default for FNVHash { + fn default() -> Self { + FNVHash(0xcbf29ce484222325) + } +} diff --git a/protocol/src/types/metadata.rs b/protocol/src/types/metadata.rs new file mode 100644 index 0000000..17c18eb --- /dev/null +++ b/protocol/src/types/metadata.rs @@ -0,0 +1,964 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::collections::HashMap; +use std::marker::PhantomData; +use std::io; +use std::fmt; +use crate::protocol; +use crate::protocol::Serializable; +use crate::protocol::LenPrefixed; +use crate::format; +use crate::item; +use crate::shared::Position; +use crate::nbt; + +pub struct MetadataKey { + index: i32, + ty: PhantomData, +} + +impl MetadataKey { + #[allow(dead_code)] + fn new(index: i32) -> MetadataKey { + MetadataKey { + index, + ty: PhantomData, + } + } +} + +pub struct Metadata { + map: HashMap, +} + +impl Metadata { + pub fn new() -> Metadata { + Metadata { map: HashMap::new() } + } + + pub fn get(&self, key: &MetadataKey) -> Option<&T> { + self.map.get(&key.index).map(T::unwrap) + } + + pub fn put(&mut self, key: &MetadataKey, val: T) { + self.map.insert(key.index, val.wrap()); + } + + fn put_raw(&mut self, index: i32, val: T) { + self.map.insert(index, val.wrap()); + } + + fn read_from18(buf: &mut R) -> Result { + let mut m = Self::new(); + loop { + let ty_index = u8::read_from(buf)? as i32; + if ty_index == 0x7f { + break; + } + let index = ty_index & 0x1f; + let ty = ty_index >> 5; + + match ty { + 0 => m.put_raw(index, i8::read_from(buf)?), + 1 => m.put_raw(index, i16::read_from(buf)?), + 2 => m.put_raw(index, i32::read_from(buf)?), + 3 => m.put_raw(index, f32::read_from(buf)?), + 4 => m.put_raw(index, String::read_from(buf)?), + 5 => m.put_raw(index, Option::::read_from(buf)?), + 6 => m.put_raw(index, + [i32::read_from(buf)?, + i32::read_from(buf)?, + i32::read_from(buf)?]), + 7 => m.put_raw(index, + [f32::read_from(buf)?, + f32::read_from(buf)?, + f32::read_from(buf)?]), + _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), + } + } + Ok(m) + } + + fn write_to18(&self, buf: &mut W) -> Result<(), protocol::Error> { + for (k, v) in &self.map { + if (*k as u8) > 0x1f { + panic!("write metadata index {:x} > 0x1f", *k as u8); + } + + let ty_index: u8 = *k as u8; + const TYPE_SHIFT: usize = 5; + + match *v + { + Value::Byte(ref val) => { + u8::write_to(&(ty_index | (0 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::Short(ref val) => { + u8::write_to(&(ty_index | (1 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + + Value::Int(ref val) => { + u8::write_to(&(ty_index | (2 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::Float(ref val) => { + u8::write_to(&(ty_index | (3 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::String(ref val) => { + u8::write_to(&(ty_index | (4 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::OptionalItemStack(ref val) => { + u8::write_to(&(ty_index | (5 << TYPE_SHIFT)), buf)?; + val.write_to(buf)?; + } + Value::Vector(ref val) => { + u8::write_to(&(ty_index | (6 << TYPE_SHIFT)), buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + Value::Rotation(ref val) => { + u8::write_to(&(ty_index | (7 << TYPE_SHIFT)), buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + + _ => { + panic!("attempted to write 1.9+ metadata to 1.8"); + } + } + } + u8::write_to(&0x7f, buf)?; + Ok(()) + } + + fn read_from19(buf: &mut R) -> Result { + let mut m = Self::new(); + loop { + let index = u8::read_from(buf)? as i32; + if index == 0xFF { + break; + } + let ty = protocol::VarInt::read_from(buf)?.0; + match ty { + 0 => m.put_raw(index, i8::read_from(buf)?), + 1 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0), + 2 => m.put_raw(index, f32::read_from(buf)?), + 3 => m.put_raw(index, String::read_from(buf)?), + 4 => m.put_raw(index, format::Component::read_from(buf)?), + 5 => m.put_raw(index, Option::::read_from(buf)?), + 6 => m.put_raw(index, bool::read_from(buf)?), + 7 => m.put_raw(index, + [f32::read_from(buf)?, + f32::read_from(buf)?, + f32::read_from(buf)?]), + 8 => m.put_raw(index, Position::read_from(buf)?), + 9 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + } + 10 => m.put_raw(index, protocol::VarInt::read_from(buf)?), + 11 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + } + 12 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0 as u16), + 13 => { + let ty = u8::read_from(buf)?; + if ty != 0 { + let name = nbt::read_string(buf)?; + let tag = nbt::Tag::read_from(buf)?; + + m.put_raw(index, nbt::NamedTag(name, tag)); + } + } + _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), + } + } + Ok(m) + } + + fn write_to19(&self, buf: &mut W) -> Result<(), protocol::Error> { + for (k, v) in &self.map { + (*k as u8).write_to(buf)?; + match *v { + Value::Byte(ref val) => { + u8::write_to(&0, buf)?; + val.write_to(buf)?; + } + Value::Int(ref val) => { + u8::write_to(&1, buf)?; + protocol::VarInt(*val).write_to(buf)?; + } + Value::Float(ref val) => { + u8::write_to(&2, buf)?; + val.write_to(buf)?; + } + Value::String(ref val) => { + u8::write_to(&3, buf)?; + val.write_to(buf)?; + } + Value::FormatComponent(ref val) => { + u8::write_to(&4, buf)?; + val.write_to(buf)?; + } + Value::OptionalItemStack(ref val) => { + u8::write_to(&5, buf)?; + val.write_to(buf)?; + } + Value::Bool(ref val) => { + u8::write_to(&6, buf)?; + val.write_to(buf)?; + } + Value::Vector(ref val) => { + u8::write_to(&7, buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + Value::Position(ref val) => { + u8::write_to(&8, buf)?; + val.write_to(buf)?; + } + Value::OptionalPosition(ref val) => { + u8::write_to(&9, buf)?; + val.is_some().write_to(buf)?; + val.write_to(buf)?; + } + Value::Direction(ref val) => { + u8::write_to(&10, buf)?; + val.write_to(buf)?; + } + Value::OptionalUUID(ref val) => { + u8::write_to(&11, buf)?; + val.is_some().write_to(buf)?; + val.write_to(buf)?; + } + Value::Block(ref val) => { + u8::write_to(&11, buf)?; + protocol::VarInt(*val as i32).write_to(buf)?; + } + Value::NBTTag(ref _val) => { + u8::write_to(&13, buf)?; + // TODO: write NBT tags metadata + //nbt::Tag(*val).write_to(buf)?; + } + _ => panic!("unexpected metadata"), + } + } + u8::write_to(&0xFF, buf)?; + Ok(()) + } + + fn read_from113(buf: &mut R) -> Result { + let mut m = Self::new(); + loop { + let index = u8::read_from(buf)? as i32; + if index == 0xFF { + break; + } + let ty = protocol::VarInt::read_from(buf)?.0; + match ty { + 0 => m.put_raw(index, i8::read_from(buf)?), + 1 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0), + 2 => m.put_raw(index, f32::read_from(buf)?), + 3 => m.put_raw(index, String::read_from(buf)?), + 4 => m.put_raw(index, format::Component::read_from(buf)?), + 5 => m.put_raw(index, LenPrefixed::::read_from(buf)?), + 6 => m.put_raw(index, Option::::read_from(buf)?), + 7 => m.put_raw(index, bool::read_from(buf)?), + 8 => m.put_raw(index, + [f32::read_from(buf)?, + f32::read_from(buf)?, + f32::read_from(buf)?]), + 9 => m.put_raw(index, Position::read_from(buf)?), + 10 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + } + 11 => m.put_raw(index, protocol::VarInt::read_from(buf)?), + 12 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + } + 13 => m.put_raw(index, protocol::VarInt::read_from(buf)?.0 as u16), + 14 => { + let ty = u8::read_from(buf)?; + if ty != 0 { + let name = nbt::read_string(buf)?; + let tag = nbt::Tag::read_from(buf)?; + + m.put_raw(index, nbt::NamedTag(name, tag)); + } + } + 15 => panic!("TODO: particle"), + 16 => m.put_raw(index, VillagerData::read_from(buf)?), + 17 => { + if bool::read_from(buf)? { + m.put_raw(index, Option::::read_from(buf)?); + } else { + m.put_raw::>(index, None); + } + }, + 18 => m.put_raw(index, PoseData::read_from(buf)?), + _ => return Err(protocol::Error::Err("unknown metadata type".to_owned())), + } + } + Ok(m) + } + + fn write_to113(&self, buf: &mut W) -> Result<(), protocol::Error> { + for (k, v) in &self.map { + (*k as u8).write_to(buf)?; + match *v { + Value::Byte(ref val) => { + u8::write_to(&0, buf)?; + val.write_to(buf)?; + } + Value::Int(ref val) => { + u8::write_to(&1, buf)?; + protocol::VarInt(*val).write_to(buf)?; + } + Value::Float(ref val) => { + u8::write_to(&2, buf)?; + val.write_to(buf)?; + } + Value::String(ref val) => { + u8::write_to(&3, buf)?; + val.write_to(buf)?; + } + Value::FormatComponent(ref val) => { + u8::write_to(&4, buf)?; + val.write_to(buf)?; + } + Value::OptionalFormatComponent(ref val) => { + u8::write_to(&5, buf)?; + val.write_to(buf)?; + } + Value::OptionalItemStack(ref val) => { + u8::write_to(&6, buf)?; + val.write_to(buf)?; + } + Value::Bool(ref val) => { + u8::write_to(&7, buf)?; + val.write_to(buf)?; + } + Value::Vector(ref val) => { + u8::write_to(&8, buf)?; + val[0].write_to(buf)?; + val[1].write_to(buf)?; + val[2].write_to(buf)?; + } + Value::Position(ref val) => { + u8::write_to(&9, buf)?; + val.write_to(buf)?; + } + Value::OptionalPosition(ref val) => { + u8::write_to(&10, buf)?; + val.is_some().write_to(buf)?; + val.write_to(buf)?; + } + Value::Direction(ref val) => { + u8::write_to(&11, buf)?; + val.write_to(buf)?; + } + Value::OptionalUUID(ref val) => { + u8::write_to(&12, buf)?; + val.is_some().write_to(buf)?; + val.write_to(buf)?; + } + Value::Block(ref val) => { + u8::write_to(&13, buf)?; + protocol::VarInt(*val as i32).write_to(buf)?; + } + Value::NBTTag(ref _val) => { + u8::write_to(&14, buf)?; + // TODO: write NBT tags metadata + //nbt::Tag(*val).write_to(buf)?; + } + Value::Particle(ref val) => { + u8::write_to(&15, buf)?; + val.write_to(buf)?; + } + Value::Villager(ref val) => { + u8::write_to(&16, buf)?; + val.write_to(buf)?; + } + Value::OptionalVarInt(ref val) => { + u8::write_to(&17, buf)?; + val.write_to(buf)?; + } + Value::Pose(ref val) => { + u8::write_to(&18, buf)?; + val.write_to(buf)?; + } + _ => panic!("unexpected metadata"), + } + } + u8::write_to(&0xFF, buf)?; + Ok(()) + } + + +} + +impl Serializable for Metadata { + fn read_from(buf: &mut R) -> Result { + let protocol_version = protocol::current_protocol_version(); + + if protocol_version >= 404 { + Metadata::read_from113(buf) + } else if protocol_version >= 74 { + Metadata::read_from19(buf) + } else { + Metadata::read_from18(buf) + } + } + + fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { + let protocol_version = protocol::current_protocol_version(); + + if protocol_version >= 404 { + self.write_to113(buf) + } else if protocol_version >= 74 { + self.write_to19(buf) + } else { + self.write_to18(buf) + } + } +} + +impl fmt::Debug for Metadata { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Metadata[ ")?; + for (k, v) in &self.map { + write!(f, "{:?}={:?}, ", k, v)?; + } + write!(f, "]") + } +} + +impl Default for Metadata { + fn default() -> Metadata { + Metadata::new() + } +} + +#[derive(Debug)] +pub enum Value { + Byte(i8), + Short(i16), + Int(i32), + Float(f32), + String(String), + FormatComponent(format::Component), + OptionalFormatComponent(LenPrefixed), + OptionalItemStack(Option), + Bool(bool), + Vector([f32; 3]), + Rotation([i32; 3]), + Position(Position), + OptionalPosition(Option), + Direction(protocol::VarInt), // TODO: Proper type + OptionalUUID(Option), + Block(u16), // TODO: Proper type + NBTTag(nbt::NamedTag), + Particle(ParticleData), + Villager(VillagerData), + OptionalVarInt(Option), + Pose(PoseData), +} + +#[derive(Debug)] +pub enum ParticleData { + AmbientEntityEffect, + AngryVillager, + Barrier, + Block { + block_state: protocol::VarInt, + }, + Bubble, + Cloud, + Crit, + DamageIndicator, + DragonBreath, + DrippingLava, + DrippingWater, + Dust { + red: f32, + green: f32, + blue: f32, + scale: f32, + }, + Effect, + ElderGuardian, + EnchantedHit, + Enchant, + EndRod, + EntityEffect, + ExplosionEmitter, + Explosion, + FallingDust { + block_state: protocol::VarInt, + }, + Firework, + Fishing, + Flame, + HappyVillager, + Heart, + InstantEffect, + Item { + item: Option, + }, + ItemSlime, + ItemSnowball, + LargeSmoke, + Lava, + Mycelium, + Note, + Poof, + Portal, + Rain, + Smoke, + Spit, + SquidInk, + SweepAttack, + TotemOfUndying, + Underwater, + Splash, + Witch, + BubblePop, + CurrentDown, + BubbleColumnUp, + Nautilus, + Dolphin, +} + +impl Serializable for ParticleData { + fn read_from(buf: &mut R) -> Result { + let id = protocol::VarInt::read_from(buf)?.0; + Ok(match id { + 0 => ParticleData::AmbientEntityEffect, + 1 => ParticleData::AngryVillager, + 2 => ParticleData::Barrier, + 3 => ParticleData::Block { + block_state: Serializable::read_from(buf)? + }, + 4 => ParticleData::Bubble, + 5 => ParticleData::Cloud, + 6 => ParticleData::Crit, + 7 => ParticleData::DamageIndicator, + 8 => ParticleData::DragonBreath, + 9 => ParticleData::DrippingLava, + 10 => ParticleData::DrippingWater, + 11 => ParticleData::Dust { + red: Serializable::read_from(buf)?, + green: Serializable::read_from(buf)?, + blue: Serializable::read_from(buf)?, + scale: Serializable::read_from(buf)?, + }, + 12 => ParticleData::Effect, + 13 => ParticleData::ElderGuardian, + 14 => ParticleData::EnchantedHit, + 15 => ParticleData::Enchant, + 16 => ParticleData::EndRod, + 17 => ParticleData::EntityEffect, + 18 => ParticleData::ExplosionEmitter, + 19 => ParticleData::Explosion, + 20 => ParticleData::FallingDust { + block_state: Serializable::read_from(buf)?, + }, + 21 => ParticleData::Firework, + 22 => ParticleData::Fishing, + 23 => ParticleData::Flame, + 24 => ParticleData::HappyVillager, + 25 => ParticleData::Heart, + 26 => ParticleData::InstantEffect, + 27 => ParticleData::Item { + item: Serializable::read_from(buf)?, + }, + 28 => ParticleData::ItemSlime, + 29 => ParticleData::ItemSnowball, + 30 => ParticleData::LargeSmoke, + 31 => ParticleData::Lava, + 32 => ParticleData::Mycelium, + 33 => ParticleData::Note, + 34 => ParticleData::Poof, + 35 => ParticleData::Portal, + 36 => ParticleData::Rain, + 37 => ParticleData::Smoke, + 38 => ParticleData::Spit, + 39 => ParticleData::SquidInk, + 40 => ParticleData::SweepAttack, + 41 => ParticleData::TotemOfUndying, + 42 => ParticleData::Underwater, + 43 => ParticleData::Splash, + 44 => ParticleData::Witch, + 45 => ParticleData::BubblePop, + 46 => ParticleData::CurrentDown, + 47 => ParticleData::BubbleColumnUp, + 48 => ParticleData::Nautilus, + 49 => ParticleData::Dolphin, + _ => panic!("unrecognized particle data id {}", id), + }) + } + + fn write_to(&self, _buf: &mut W) -> Result<(), protocol::Error> { + unimplemented!() + } +} + +#[derive(Debug)] +pub struct VillagerData { + villager_type: protocol::VarInt, + profession: protocol::VarInt, + level: protocol::VarInt, +} + +impl Serializable for VillagerData { + fn read_from(buf: &mut R) -> Result { + let villager_type = protocol::VarInt::read_from(buf)?; + let profession = protocol::VarInt::read_from(buf)?; + let level = protocol::VarInt::read_from(buf)?; + Ok(VillagerData { villager_type, profession, level }) + } + + fn write_to(&self, _buf: &mut W) -> Result<(), protocol::Error> { + unimplemented!() + } +} + +#[derive(Debug)] +pub enum PoseData { + Standing, + FallFlying, + Sleeping, + Swimming, + SpinAttack, + Sneaking, + Dying, +} + +impl Serializable for PoseData { + fn read_from(buf: &mut R) -> Result { + let n = protocol::VarInt::read_from(buf)?; + Ok(match n.0 { + 0 => PoseData::Standing, + 1 => PoseData::FallFlying, + 2 => PoseData::Sleeping, + 3 => PoseData::Swimming, + 4 => PoseData::SpinAttack, + 5 => PoseData::Sneaking, + 6 => PoseData::Dying, + _ => panic!("unknown pose data: {}", n.0), + }) + } + + fn write_to(&self, _buf: &mut W) -> Result<(), protocol::Error> { + unimplemented!() + } +} + + + +pub trait MetaValue { + fn unwrap(_: &Value) -> &Self; + fn wrap(self) -> Value; +} + +impl MetaValue for i8 { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Byte(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Byte(self) + } +} + +impl MetaValue for i16 { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Short(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Short(self) + } +} + +impl MetaValue for i32 { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Int(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Int(self) + } +} + +impl MetaValue for f32 { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Float(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Float(self) + } +} + +impl MetaValue for String { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::String(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::String(self) + } +} + +impl MetaValue for format::Component { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::FormatComponent(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::FormatComponent(self) + } +} + +impl MetaValue for LenPrefixed { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::OptionalFormatComponent(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::OptionalFormatComponent(self) + } +} + + +impl MetaValue for Option { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::OptionalItemStack(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::OptionalItemStack(self) + } +} + +impl MetaValue for bool { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Bool(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Bool(self) + } +} + +impl MetaValue for [i32; 3] { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Rotation(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Rotation(self) + } +} + +impl MetaValue for [f32; 3] { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Vector(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Vector(self) + } +} + +impl MetaValue for Position { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Position(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Position(self) + } +} + +impl MetaValue for Option { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::OptionalPosition(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::OptionalPosition(self) + } +} + +impl MetaValue for protocol::VarInt { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Direction(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Direction(self) + } +} + +impl MetaValue for Option { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::OptionalUUID(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::OptionalUUID(self) + } +} + +impl MetaValue for u16 { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Block(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Block(self) + } +} + +impl MetaValue for nbt::NamedTag { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::NBTTag(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::NBTTag(self) + } +} + +impl MetaValue for VillagerData { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Villager(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Villager(self) + } +} + +impl MetaValue for Option { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::OptionalVarInt(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::OptionalVarInt(self) + } +} + +impl MetaValue for PoseData { + fn unwrap(value: &Value) -> &Self { + match *value { + Value::Pose(ref val) => val, + _ => panic!("incorrect key"), + } + } + fn wrap(self) -> Value { + Value::Pose(self) + } +} + + +#[cfg(test)] +mod test { + use super::*; + use std::marker::PhantomData; + + const TEST: MetadataKey = + MetadataKey { + index: 0, + ty: PhantomData, + }; + + #[test] + fn basic() { + let mut m = Metadata::new(); + + m.put(&TEST, "Hello world".to_owned()); + + match m.get(&TEST) { + Some(val) => { + assert!(val == "Hello world"); + } + None => panic!("failed"), + } + } +} diff --git a/protocol/src/types/mod.rs b/protocol/src/types/mod.rs new file mode 100644 index 0000000..5232c8c --- /dev/null +++ b/protocol/src/types/mod.rs @@ -0,0 +1,60 @@ +// Copyright 2016 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod metadata; +pub use self::metadata::*; + +pub mod bit; +pub mod nibble; +pub mod hash; + +#[derive(Clone, Copy, Debug)] +pub enum Gamemode { + Survival = 0, + Creative = 1, + Adventure = 2, + Spectator = 3, +} + +impl Gamemode { + pub fn from_int(val: i32) -> Gamemode { + match val { + 3 => Gamemode::Spectator, + 2 => Gamemode::Adventure, + 1 => Gamemode::Creative, + 0 | _ => Gamemode::Survival, + } + } + + pub fn can_fly(&self) -> bool { + match *self { + Gamemode::Creative | Gamemode::Spectator => true, + _ => false, + } + } + + pub fn always_fly(&self) -> bool { + match *self { + Gamemode::Spectator => true, + _ => false, + } + } + + pub fn noclip(&self) -> bool { + match *self { + Gamemode::Spectator => true, + _ => false, + } + } +} diff --git a/protocol/src/types/nibble.rs b/protocol/src/types/nibble.rs new file mode 100644 index 0000000..91ecdc8 --- /dev/null +++ b/protocol/src/types/nibble.rs @@ -0,0 +1,45 @@ +// Copyright 2015 Matthew Collins +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +pub struct Array { + pub data: Vec, +} + +impl Array { + pub fn new(size: usize) -> Array { + Array { + data: vec![0; (size + 1) >> 1], + } + } + + pub fn get(&self, idx: usize) -> u8 { + let val = self.data[idx>>1]; + if idx&1 == 0 { + val & 0xF + } else { + val >> 4 + } + } + + pub fn set(&mut self, idx: usize, val: u8) { + let i = idx >> 1; + let old = self.data[i]; + if idx&1 == 0 { + self.data[i] = (old & 0xF0) | (val & 0xF); + } else { + self.data[i] = (old & 0x0F) | ((val & 0xF) << 4); + } + } +}