aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoey Sacchini <joey@sacchini.net>2020-09-29 15:36:00 -0400
committerJoey Sacchini <joey@sacchini.net>2020-09-29 15:36:00 -0400
commit5b8e64e398ce5cc3cd8067545836416485b1f7ea (patch)
treec1e45b41b56e8da4984a0fbf96797ffb2b914743 /src
downloadmcproto-rs-5b8e64e398ce5cc3cd8067545836416485b1f7ea.tar.gz
mcproto-rs-5b8e64e398ce5cc3cd8067545836416485b1f7ea.tar.bz2
mcproto-rs-5b8e64e398ce5cc3cd8067545836416485b1f7ea.zip
init commit
Diffstat (limited to 'src')
-rw-r--r--src/deserialize.rs106
-rw-r--r--src/lib.rs19
-rw-r--r--src/nbt.rs534
-rw-r--r--src/protocol.rs349
-rw-r--r--src/serialize.rs24
-rw-r--r--src/status.rs115
-rw-r--r--src/testdata/bigtest.nbtbin0 -> 507 bytes
-rw-r--r--src/types.rs1020
-rw-r--r--src/utils.rs132
-rw-r--r--src/uuid.rs185
-rw-r--r--src/v1_15_2.rs2391
11 files changed, 4875 insertions, 0 deletions
diff --git a/src/deserialize.rs b/src/deserialize.rs
new file mode 100644
index 0000000..694c6e5
--- /dev/null
+++ b/src/deserialize.rs
@@ -0,0 +1,106 @@
+use crate::types::VarInt;
+use std::string::FromUtf8Error;
+
+#[derive(Debug)]
+pub enum DeserializeErr {
+ Eof,
+ VarNumTooLong(Vec<u8>),
+ NegativeLength(VarInt),
+ BadStringEncoding(FromUtf8Error),
+ InvalidBool(u8),
+ NbtUnknownTagType(u8),
+ NbtBadLength(isize),
+ NbtInvalidStartTag(u8),
+ CannotUnderstandValue(String),
+ FailedJsonDeserialize(String)
+}
+
+impl<'b, R> Into<DeserializeResult<'b, R>> for DeserializeErr {
+ #[inline]
+ fn into(self) -> DeserializeResult<'b, R> {
+ Err(self)
+ }
+}
+
+pub struct Deserialized<'b, R> {
+ pub value: R,
+ pub data: &'b [u8],
+}
+
+impl<'b, R> Into<DeserializeResult<'b, R>> for Deserialized<'b, R> {
+ #[inline]
+ fn into(self) -> DeserializeResult<'b, R> {
+ Ok(self)
+ }
+}
+
+impl<'b, R> Deserialized<'b, R> {
+ #[inline]
+ pub fn create(value: R, data: &'b [u8]) -> Self {
+ Deserialized {
+ value,
+ data,
+ }
+ }
+
+ #[inline]
+ pub fn ok(value: R, rest: &'b [u8]) -> DeserializeResult<'b, R> {
+ Self::create(value, rest).into()
+ }
+
+ #[inline]
+ pub fn replace<T>(self, other: T) -> Deserialized<'b, T> {
+ Deserialized{
+ value: other,
+ data: self.data,
+ }
+ }
+
+ #[inline]
+ pub fn map<F, T>(self, f: F) -> Deserialized<'b, T> where F: FnOnce(R) -> T {
+ Deserialized{
+ value: f(self.value),
+ data: self.data,
+ }
+ }
+
+ #[inline]
+ pub fn try_map<F, T>(self, f: F) -> DeserializeResult<'b, T> where
+ F: FnOnce(R) -> Result<T, DeserializeErr>
+ {
+ match f(self.value) {
+ Ok(new_value) => Ok(Deserialized{
+ value: new_value,
+ data: self.data,
+ }),
+ Err(err) => Err(err)
+ }
+ }
+
+ #[inline]
+ pub fn and_then<F, T>(self, f: F) -> DeserializeResult<'b, T> where
+ F: FnOnce(R, &'b[u8]) -> DeserializeResult<'b, T>
+ {
+ f(self.value, self.data)
+ }
+}
+
+
+impl<'b, R> From<(R, &'b [u8])> for Deserialized<'b, R> {
+ fn from(v: (R, &'b [u8])) -> Self {
+ let (value, data) = v;
+ Deserialized {
+ value,
+ data,
+ }
+ }
+}
+
+pub type DeserializeResult<'b, R>
+= Result<
+ Deserialized<'b, R>,
+ DeserializeErr>;
+
+pub trait Deserialize: Sized {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<Self>;
+} \ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b04090f
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,19 @@
+#![feature(impl_trait_in_bindings)]
+#![feature(const_fn)]
+#![feature(test)]
+
+#[cfg(test)]
+extern crate test;
+
+mod serialize;
+mod deserialize;
+pub mod utils;
+pub mod protocol;
+pub mod uuid;
+pub mod nbt;
+pub mod types;
+pub mod v1_15_2;
+pub mod status;
+
+pub use serialize::*;
+pub use deserialize::*; \ No newline at end of file
diff --git a/src/nbt.rs b/src/nbt.rs
new file mode 100644
index 0000000..ac2d39c
--- /dev/null
+++ b/src/nbt.rs
@@ -0,0 +1,534 @@
+use std::fmt;
+use crate::{DeserializeResult, DeserializeErr, Deserialized};
+use crate::utils::{read_short, take, read_int, read_long, read_one_byte, write_long, write_int, write_short};
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct NamedTag {
+ pub name: String,
+ pub payload: Tag,
+}
+
+impl NamedTag {
+ pub fn root_compound_tag_from_bytes(data: &[u8]) -> DeserializeResult<NamedTag> {
+ read_nbt_data(data)
+ }
+
+ pub fn is_end(&self) -> bool {
+ match self.payload {
+ Tag::End => true,
+ _ => false,
+ }
+ }
+}
+
+impl fmt::Display for NamedTag {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_fmt(format_args!("TAG_{}('{}'): ", self.payload.tag_type_name(), self.name))?;
+ self.payload.write_contents(f)
+ }
+}
+
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum Tag {
+ Byte(i8),
+ Short(i16),
+ Int(i32),
+ Long(i64),
+ Float(f32),
+ Double(f64),
+ ByteArray(Vec<u8>),
+ String(String),
+ List(Vec<Tag>),
+ Compound(Vec<NamedTag>),
+ IntArray(Vec<i32>),
+ LongArray(Vec<i64>),
+ End,
+}
+
+impl fmt::Display for Tag {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_fmt(format_args!("TAG_{}: ", self.tag_type_name()))?;
+ self.write_contents(f)
+ }
+}
+
+impl Tag {
+ pub fn with_name(self, name: &str) -> NamedTag {
+ NamedTag {
+ name: name.into(),
+ payload: self,
+ }
+ }
+
+ pub fn tag_type_name(&self) -> &str {
+ match self {
+ Tag::Byte(_) => "Byte",
+ Tag::Short(_) => "Short",
+ Tag::Int(_) => "Int",
+ Tag::Long(_) => "Long",
+ Tag::Float(_) => "Float",
+ Tag::Double(_) => "Double",
+ Tag::ByteArray(_) => "Byte_Array",
+ Tag::String(_) => "String",
+ Tag::List(_) => "List",
+ Tag::Compound(_) => "Compound",
+ Tag::IntArray(_) => "Int_Array",
+ Tag::LongArray(_) => "Long_Array",
+ Tag::End => "END",
+ }
+ }
+
+ fn write_contents(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Tag::Byte(v) => f.write_fmt(format_args!("{}", *v)),
+ Tag::Short(v) => f.write_fmt(format_args!("{}", *v)),
+ Tag::Int(v) => f.write_fmt(format_args!("{}", *v)),
+ Tag::Long(v) => f.write_fmt(format_args!("{}L", *v)),
+ Tag::Float(v) => f.write_fmt(format_args!("{}", *v)),
+ Tag::Double(v) => f.write_fmt(format_args!("{}", *v)),
+ Tag::ByteArray(v) => f.write_fmt(format_args!("[{} bytes]", v.len())),
+ Tag::String(v) => f.write_fmt(format_args!("\"{}\"", v)),
+ Tag::List(v) => {
+ let out = write_contents(v);
+ f.write_str(out.as_str())
+ }
+ Tag::Compound(v) => {
+ let out = write_contents(v);
+ f.write_str(out.as_str())
+ }
+ Tag::IntArray(v) => f.write_fmt(format_args!("[{} ints]", v.len())),
+ Tag::LongArray(v) => f.write_fmt(format_args!("[{} longs]", v.len())),
+ Tag::End => f.write_str("END"),
+ }
+ }
+}
+
+#[inline]
+fn write_contents<F>(contents: &Vec<F>) -> String where F: fmt::Display {
+ format!("{} entries\n{{\n{}\n}}", contents.len(), contents.iter()
+ .flat_map(move |elem| elem.to_string().split("\n").map(String::from).collect::<Vec<String>>())
+ .map(move |line| " ".to_owned() + line.as_str())
+ .collect::<Vec<String>>()
+ .join("\n"))
+}
+
+// deserialization first
+
+// reads from the root level
+fn read_nbt_data(data: &[u8]) -> DeserializeResult<NamedTag> {
+ let Deserialized { value: tag_type_id, data: _ } = read_one_byte(data)?;
+ match tag_type_id {
+ 0x0A => read_named_tag(data),
+ other => Err(DeserializeErr::NbtInvalidStartTag(other)),
+ }
+}
+
+// reads any named tag: read id -> read name -> read tag with id -> name tag with name
+#[inline]
+pub fn read_named_tag(data: &[u8]) -> DeserializeResult<NamedTag> {
+ let Deserialized { value: tag_type_id, data } = read_one_byte(data)?;
+ if tag_type_id == 0x00 { // tag end
+ Deserialized::ok(Tag::End.with_name(""), data)
+ } else {
+ let Deserialized { value: name, data } = read_string(data)?;
+ Ok(read_tag(tag_type_id, data)?.map(move |payload| NamedTag { name, payload }))
+ }
+}
+
+// reads any tag (given it's id)
+#[inline]
+pub fn read_tag(tag_type_id: u8, data: &[u8]) -> DeserializeResult<Tag> {
+ match tag_type_id {
+ 0x00 => Deserialized::ok(Tag::End, data),
+ 0x01 => read_tag_byte(data),
+ 0x02 => read_tag_short(data),
+ 0x03 => read_tag_int(data),
+ 0x04 => read_tag_long(data),
+ 0x05 => read_tag_float(data),
+ 0x06 => read_tag_double(data),
+ 0x07 => read_tag_byte_array(data),
+ 0x08 => read_tag_string(data),
+ 0x09 => read_tag_list(data),
+ 0x0A => read_tag_compound(data),
+ 0x0B => read_tag_int_array(data),
+ 0x0C => read_tag_long_array(data),
+ other => Err(DeserializeErr::NbtUnknownTagType(other)),
+ }
+}
+
+#[inline]
+fn read_tag_byte(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_one_byte(data)?.map(move |byte| Tag::Byte(byte as i8)))
+}
+
+#[inline]
+fn read_tag_short(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_short(data)?.map(move |i| Tag::Short(i as i16)))
+}
+
+#[inline]
+fn read_tag_int(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_int(data)?.map(move |i| Tag::Int(i as i32)))
+}
+
+#[inline]
+fn read_tag_long(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_long(data)?.map(move |i| Tag::Long(i as i64)))
+}
+
+#[inline]
+fn read_tag_float(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_int(data)?.map(move |i| Tag::Float(f32::from_bits(i as u32))))
+}
+
+#[inline]
+fn read_tag_double(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_long(data)?.map(move |i| Tag::Double(f64::from_bits(i as u64))))
+}
+
+#[inline]
+fn read_tag_byte_array(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_int(data)?.and_then(move |size, rest| take(size as usize)(rest))?
+ .map(move |arr| Tag::ByteArray(Vec::from(arr))))
+}
+
+#[inline]
+fn read_tag_string(data: &[u8]) -> DeserializeResult<Tag> {
+ Ok(read_string(data)?.map(move |str| Tag::String(str)))
+}
+
+fn read_tag_list(data: &[u8]) -> DeserializeResult<Tag> {
+ let Deserialized { value: contents_tag_type_id, data } = read_one_byte(data)?;
+ let Deserialized { value: list_length, data } = read_int(data)?;
+ if list_length <= 0 {
+ if contents_tag_type_id != 0x00 {
+ Err(DeserializeErr::NbtBadLength(list_length as isize))
+ } else {
+ Deserialized::ok(Tag::List(vec!()), data)
+ }
+ } else {
+ let mut out_vec = Vec::with_capacity(list_length as usize);
+ let mut remaining_data = data;
+ for _ in 0..list_length {
+ let Deserialized { value: element, data: rest } = read_tag(contents_tag_type_id, &remaining_data)?;
+ out_vec.push(element);
+ remaining_data = rest;
+ }
+
+ Deserialized::ok(Tag::List(out_vec), remaining_data)
+ }
+}
+
+fn read_tag_compound(data: &[u8]) -> DeserializeResult<Tag> {
+ let mut out = Vec::new();
+ let mut remaining_data = data;
+ loop {
+ let Deserialized { value: elem, data: rest } = read_named_tag(remaining_data)?;
+ remaining_data = rest;
+ if elem.is_end() {
+ break;
+ }
+ out.push(elem);
+ }
+
+ Deserialized::ok(Tag::Compound(out), remaining_data)
+}
+
+#[inline]
+fn read_tag_int_array(data: &[u8]) -> DeserializeResult<Tag> {
+ read_array_tag(
+ data,
+ move |data| Ok(read_int(data)?.map(move |r| r as i32)),
+ Tag::IntArray)
+}
+
+#[inline]
+fn read_tag_long_array(data: &[u8]) -> DeserializeResult<Tag> {
+ read_array_tag(
+ data,
+ move |data| Ok(read_long(data)?.map(move |r| r as i64)),
+ Tag::LongArray)
+}
+
+#[inline]
+fn read_array_tag<'a, R, F, M>(data: &'a [u8], parser: F, finalizer: M) -> DeserializeResult<'a, Tag> where
+ F: Fn(&'a [u8]) -> DeserializeResult<'a, R>,
+ M: Fn(Vec<R>) -> Tag
+{
+ let Deserialized { value: count, data } = read_int(data)?.map(move |v| v as i32);
+ if count < 0 {
+ Err(DeserializeErr::NbtBadLength(count as isize))
+ } else {
+ let mut out = Vec::with_capacity(count as usize);
+ let mut data_remaining = data;
+ for _ in 0..count {
+ let Deserialized { value: elem, data: rest } = parser(data_remaining)?;
+ data_remaining = rest;
+ out.push(elem);
+ }
+
+ Deserialized::ok(finalizer(out), data_remaining)
+ }
+}
+
+#[inline]
+fn read_string(data: &[u8]) -> DeserializeResult<String> {
+ read_short(data)?
+ .and_then(move |length, data|
+ take(length as usize)(data))?
+ .try_map(move |bytes|
+ String::from_utf8(Vec::from(bytes)).map_err(move |err| {
+ DeserializeErr::BadStringEncoding(err)
+ }))
+}
+
+// serialize
+impl NamedTag {
+ pub fn bytes(&self) -> Vec<u8> {
+ let type_id = self.payload.id();
+ if type_id == 0x00 {
+ vec!(0x00)
+ } else {
+ let payload_bytes = self.payload.bytes();
+ let name_len = self.name.len();
+ let name_len_bytes = write_short(name_len as u16);
+ let mut out = Vec::with_capacity(1 + name_len_bytes.len() + name_len + payload_bytes.len());
+ out.push(type_id);
+ out.extend_from_slice(&name_len_bytes);
+ out.extend(self.name.bytes());
+ out.extend(payload_bytes);
+ out
+ }
+ }
+}
+
+impl Tag {
+ pub fn id(&self) -> u8 {
+ match self {
+ Tag::Byte(_) => 0x01,
+ Tag::Short(_) => 0x02,
+ Tag::Int(_) => 0x03,
+ Tag::Long(_) => 0x04,
+ Tag::Float(_) => 0x05,
+ Tag::Double(_) => 0x06,
+ Tag::ByteArray(_) => 0x07,
+ Tag::String(_) => 0x08,
+ Tag::List(_) => 0x09,
+ Tag::Compound(_) => 0x0A,
+ Tag::IntArray(_) => 0x0B,
+ Tag::LongArray(_) => 0x0C,
+ Tag::End => 0x00,
+ }
+ }
+
+ pub fn bytes(&self) -> Vec<u8> {
+ match self {
+ Tag::Byte(b) => vec!(*b as u8),
+ Tag::Short(v) => Vec::from(write_short(*v as u16)),
+ Tag::Int(v) => Vec::from(write_int(*v as u32)),
+ Tag::Long(v) => Vec::from(write_long(*v as u64)),
+ Tag::Float(v) => Vec::from(write_int(v.to_bits())),
+ Tag::Double(v) => Vec::from(write_long(v.to_bits())),
+ Tag::ByteArray(v) => {
+ let n = v.len();
+ let mut out = Vec::with_capacity(n + 4);
+ let size_bytes = write_int(n as u32);
+ out.extend_from_slice(&size_bytes);
+ out.extend(v);
+ out
+ }
+ Tag::String(v) => {
+ let n = v.len();
+ let mut out = Vec::with_capacity(n + 2);
+ let size_bytes = write_short(n as u16);
+ out.extend_from_slice(&size_bytes);
+ out.extend(v.bytes());
+ out
+ }
+ Tag::List(v) => {
+ let count = v.len();
+ let elem_id = {
+ if count == 0 {
+ 0x00
+ } else {
+ let mut id = None;
+ for elem in v {
+ let elem_id = elem.id();
+ if let Some(old_id) = id.replace(elem_id) {
+ if old_id != elem_id {
+ panic!("list contains tags of different types, cannot serialize");
+ }
+ }
+ }
+
+ id.expect("there must be some elements in the list")
+ }
+ };
+
+ let mut out = Vec::new();
+ out.push(elem_id);
+ let count_bytes = write_int(count as u32);
+ out.extend_from_slice(&count_bytes);
+ out.extend(v.iter().flat_map(move |elem| elem.bytes().into_iter()));
+ out
+ }
+ Tag::Compound(v) => {
+ let mut out = Vec::new();
+ for elem in v {
+ out.extend(elem.bytes());
+ }
+ out.extend(Tag::End.with_name("").bytes());
+ out
+ }
+ Tag::IntArray(v) => {
+ let n = v.len();
+ let mut out = Vec::with_capacity(4 + (4 * n));
+ let n_bytes = write_int(n as u32);
+ out.extend_from_slice(&n_bytes);
+ for value in v {
+ let bytes = write_int(*value as u32);
+ out.extend_from_slice(&bytes);
+ }
+ out
+ }
+ Tag::LongArray(v) => {
+ let n = v.len();
+ let mut out = Vec::with_capacity(4 + (8 * n));
+ let n_bytes = write_int(n as u32);
+ out.extend_from_slice(&n_bytes);
+ for value in v {
+ let bytes = write_long(*value as u64);
+ out.extend_from_slice(&bytes);
+ }
+ out
+ }
+ Tag::End => Vec::default(),
+ }
+ }
+}
+
+// test
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Read;
+ use flate2::read::GzDecoder;
+ use std::fs::File;
+
+ #[test]
+ fn test_read_bignbt_example() {
+ let actual = read_bigtest();
+
+ let expected = Tag::Compound(vec!(
+ Tag::Long(9223372036854775807).with_name("longTest"),
+ Tag::Short(32767).with_name("shortTest"),
+ Tag::String("HELLO WORLD THIS IS A TEST STRING ÅÄÖ!".into()).with_name("stringTest"),
+ Tag::Float(0.49823147).with_name("floatTest"),
+ Tag::Int(2147483647).with_name("intTest"),
+ Tag::Compound(vec!(
+ Tag::Compound(vec!(
+ Tag::String("Hampus".into()).with_name("name"),
+ Tag::Float(0.75).with_name("value"),
+ )).with_name("ham"),
+ Tag::Compound(vec!(
+ Tag::String("Eggbert".into()).with_name("name"),
+ Tag::Float(0.5).with_name("value"),
+ )).with_name("egg")
+ )).with_name("nested compound test"),
+ Tag::List(vec!(
+ Tag::Long(11),
+ Tag::Long(12),
+ Tag::Long(13),
+ Tag::Long(14),
+ Tag::Long(15),
+ )).with_name("listTest (long)"),
+ Tag::List(vec!(
+ Tag::Compound(vec!(
+ Tag::String("Compound tag #0".into()).with_name("name"),
+ Tag::Long(1264099775885).with_name("created-on"),
+ )),
+ Tag::Compound(vec!(
+ Tag::String("Compound tag #1".into()).with_name("name"),
+ Tag::Long(1264099775885).with_name("created-on"),
+ ))
+ )).with_name("listTest (compound)"),
+ Tag::Byte(127).with_name("byteTest"),
+ Tag::ByteArray(bigtest_generate_byte_array()).with_name("byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))"),
+ Tag::Double(0.4931287132182315).with_name("doubleTest")
+ )).with_name("Level");
+
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn test_serialize_bigtest() {
+ let (unzipped, result) = read_bigtest_with_bytes();
+ let serialized = result.bytes();
+ assert_eq!(unzipped, serialized);
+ let Deserialized{value: unserialized, data: _} = NamedTag::root_compound_tag_from_bytes(serialized.as_slice()).expect("deserialize serialized nbt");
+ assert_eq!(unserialized, result);
+ }
+
+ #[test]
+ fn test_int_array() {
+ let original = Tag::Compound(vec!(
+ Tag::IntArray(vec!(1, 2, -5, 123127, -12373, 0, 0, 4, 2)).with_name("test ints")
+ )).with_name("test");
+
+ let bytes = original.bytes();
+ let Deserialized{value: unserialized, data: _} = NamedTag::root_compound_tag_from_bytes(bytes.as_slice()).expect("deserialize int array");
+ assert_eq!(original, unserialized);
+ }
+
+ #[test]
+ fn test_long_array() {
+ let original = Tag::Compound(vec!(
+ Tag::LongArray(vec!(1, 2, -5, 123127999999, -1237399999, 0, 0, 4, 2)).with_name("test ints")
+ )).with_name("test");
+
+ let bytes = original.bytes();
+ let Deserialized{value: unserialized, data: _} = NamedTag::root_compound_tag_from_bytes(bytes.as_slice()).expect("deserialize int array");
+ assert_eq!(original, unserialized);
+ }
+
+ #[test]
+ fn test_display() {
+ println!("{}", read_bigtest());
+ }
+
+ #[test]
+ fn test_debug() {
+ println!("{:?}", read_bigtest());
+ }
+
+ fn read_bigtest_with_bytes() -> (Vec<u8>, NamedTag) {
+ let unzipped = read_compressed_file("src/testdata/bigtest.nbt").expect("read nbt data");
+ let Deserialized{value: result, data: rest} = NamedTag::root_compound_tag_from_bytes(unzipped.as_slice()).expect("deserialize nbt");
+ assert_eq!(rest.len(), 0);
+
+ (unzipped, result)
+ }
+
+ fn read_bigtest() -> NamedTag {
+ let (_, result) = read_bigtest_with_bytes();
+ result
+ }
+
+ fn bigtest_generate_byte_array() -> Vec<u8> {
+ const COUNT: usize = 1000;
+ let mut out = Vec::with_capacity(COUNT);
+ for i in 0..COUNT {
+ out.push((((i * i * 255) + (i * 7)) % 100) as u8);
+ }
+ out
+ }
+
+ fn read_compressed_file(at: &str) -> std::io::Result<Vec<u8>> {
+ let file = File::open(at)?;
+ let mut gz = GzDecoder::new(file);
+ let mut out = Vec::new();
+ gz.read_to_end(&mut out)?;
+ Ok(out)
+ }
+} \ No newline at end of file
diff --git a/src/protocol.rs b/src/protocol.rs
new file mode 100644
index 0000000..01265df
--- /dev/null
+++ b/src/protocol.rs
@@ -0,0 +1,349 @@
+use crate::{Serialize, Deserialize, DeserializeErr};
+use std::fmt::Debug;
+
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct ProtocolSpec {
+ pub name: String,
+ pub packets: Vec<ProtocolPacketSpec>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct ProtocolPacketSpec {
+ pub state: String,
+ pub direction: String,
+ pub id: i32,
+ pub name: String,
+ pub fields: Vec<ProtocolPacketField>,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct ProtocolPacketField {
+ pub name: String,
+ pub kind: String,
+}
+
+pub trait PacketIdentifier: Clone + Debug + PartialEq + Serialize {}
+
+impl<T: Clone + Debug + PartialEq + Serialize> PacketIdentifier for T {}
+
+pub trait Packet<I: PacketIdentifier>: Serialize {
+ fn id(&self) -> I;
+
+ fn mc_deserialize(raw: RawPacket<I>) -> Result<Self, PacketErr>;
+}
+
+#[derive(Debug)]
+pub enum PacketErr {
+ UnknownId(i32),
+ DeserializeFailed(DeserializeErr)
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct RawPacket<I> {
+ pub id: I,
+ pub data: Vec<u8>,
+}
+
+pub trait ProtocolType: Serialize + Deserialize {}
+
+impl<T: Serialize + Deserialize> ProtocolType for T {}
+
+#[macro_export]
+macro_rules! as_item {
+ ($i:item) => { $i };
+}
+
+#[macro_export]
+macro_rules! __protocol_body_serialize_def_helper {
+ ($to: ident, $slf: ident, $fieldn: ident, $($field_rest: ident),+) => {
+ $to.serialize_other(&$slf.$fieldn)?;
+ $crate::__protocol_body_serialize_def_helper!($to, $slf, $($field_rest),+);
+ };
+
+ ( $to: ident, $slf: ident, $fieldn: ident ) => {
+ $to.serialize_other(&$slf.$fieldn)
+ };
+}
+
+#[macro_export]
+macro_rules! __protocol_body_def_helper {
+ ($bodyt: ident { }) => {
+ #[derive(Debug, Clone, PartialEq, Default)]
+ pub struct $bodyt;
+
+ impl Serialize for $bodyt {
+ fn mc_serialize<S: Serializer>(&self, _: &mut S) -> SerializeResult {
+ Ok(())
+ }
+ }
+
+ impl Deserialize for $bodyt {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Deserialized::ok(Self::default(), data)
+ }
+ }
+ };
+ ($bodyt: ident { $($fname: ident: $ftyp: ty ),+ }) => {
+ $crate::as_item! {
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct $bodyt {
+ $(pub $fname: $ftyp),+
+ }
+ }
+
+ impl Serialize for $bodyt {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ $(
+ to.serialize_other(&self.$fname)?;
+ )+
+ Ok(())
+ }
+ }
+
+ impl Deserialize for $bodyt {
+ fn mc_deserialize(_rest: &[u8]) -> DeserializeResult<'_, Self> {
+ $(let Deserialized{ value: $fname, data: _rest } = <$ftyp>::mc_deserialize(_rest)?;)+
+
+ Deserialized::ok(Self{ $($fname),+ }, _rest)
+ }
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! define_protocol {
+ ($packett: ident, $directiont: ident, $statet: ident, $idt: ident, $idi: ident => { $($nam: ident, $id: literal, $state: ident, $direction: ident => $body: ident { $($fnam: ident: $ftyp: ty),* }),*}) => {
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ pub struct $idi {
+ pub id: $idt,
+ pub state: $statet,
+ pub direction: $directiont
+ }
+
+ impl crate::Serialize for $idi {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ VarInt(self.id).mc_serialize(to)
+ }
+ }
+
+ impl From<($idt, $statet, $directiont)> for $idi {
+ fn from(tuple: ($idt, $statet, $directiont)) -> Self {
+ let (id, state, direction) = tuple;
+ Id { id, state, direction }
+ }
+ }
+
+ $crate::as_item! {
+ #[derive(Debug, PartialEq, Clone)]
+ pub enum $packett {
+ $($nam($body)),*,
+ }
+ }
+
+ impl crate::protocol::Packet<$idi> for $packett {
+ fn id(&self) -> $idi {
+ use self::$packett::*;
+ use self::$statet::*;
+ use self::$directiont::*;
+
+ match self {
+ $($nam(_) => ($id, $state, $direction)),*
+ }.into()
+ }
+
+ fn mc_deserialize(raw: crate::protocol::RawPacket<$idi>) ->
+ Result<Self, crate::protocol::PacketErr>
+ {
+ use self::$packett::*;
+ use self::$statet::*;
+ use self::$directiont::*;
+ use crate::protocol::PacketErr::*;
+ use crate::Deserialize;
+
+ let id = raw.id;
+ let data = raw.data.as_slice();
+
+ match (id.id, id.state, id.direction) {
+ $(($id, $state, $direction) => Ok($nam($body::mc_deserialize(data).map_err(DeserializeFailed)?.value))),*,
+ other => Err(UnknownId(other.0)),
+ }
+ }
+ }
+
+ impl crate::Serialize for $packett {
+ fn mc_serialize<S: crate::Serializer>(&self, to: &mut S) -> crate::SerializeResult {
+ use self::$packett::*;
+ match self {
+ $($nam(body) => to.serialize_other(body)),+
+ }
+ }
+ }
+
+ impl $packett {
+ pub fn describe() -> crate::protocol::ProtocolSpec {
+ crate::protocol::ProtocolSpec {
+ name: stringify!($packett).to_owned(),
+ packets: vec!(
+ $(crate::protocol::ProtocolPacketSpec{
+ state: stringify!($state).to_owned(),
+ direction: stringify!($direction).to_owned(),
+ id: $id,
+ name: stringify!($nam).to_owned(),
+ fields: vec!(
+ $(crate::protocol::ProtocolPacketField{
+ name: stringify!($fnam).to_owned(),
+ kind: stringify!($ftyp).to_owned(),
+ }),*
+ )
+ }),*,
+ )
+ }
+ }
+ }
+
+ $($crate::__protocol_body_def_helper!($body { $($fnam: $ftyp),* });)*
+ };
+}
+
+#[macro_export]
+macro_rules! proto_enum_with_type {
+ ($typ: ty, $from_nam: ident, $as_nam: ident, $fmt: literal, $typname: ident, $(($bval: literal, $nam: ident)),*) => {
+ $crate::as_item! {
+ #[derive(PartialEq, Clone, Copy)]
+ pub enum $typname {
+ $($nam),*
+ }
+ }
+
+ impl Serialize for $typname {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&self.$as_nam())
+ }
+ }
+
+ impl Deserialize for $typname {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ <$typ>::mc_deserialize(data)?.and_then(move |id, rest| {
+ Self::$from_nam(id).map(move |val| {
+ Deserialized::ok(val, rest)
+ }).unwrap_or_else(|| Err(DeserializeErr::CannotUnderstandValue(format!("invalid {} {}", stringify!($typname), id))))
+ })
+ }
+ }
+
+ impl Into<$typ> for $typname {
+ fn into(self) -> $typ {
+ use $typname::*;
+ match self {
+ $($nam => $bval.into()),*,
+ }
+ }
+ }
+
+ impl std::fmt::Display for $typname {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, $fmt, self.name(), self.$as_nam())?;
+ Ok(())
+ }
+ }
+
+ impl std::fmt::Debug for $typname {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, $fmt, self.name(), self.$as_nam())?;
+ Ok(())
+ }
+ }
+
+ impl $typname {
+ pub fn $from_nam(b: $typ) -> Option<Self> {
+ use $typname::*;
+ match b.into() {
+ $($bval => Some($nam)),*,
+ _ => None
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ use $typname::*;
+ match self {
+ $($nam => stringify!($nam)),+,
+ }
+ }
+
+ pub fn $as_nam(&self) -> $typ {
+ (*self).into()
+ }
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! proto_byte_enum {
+ ($typname: ident, $($bval: literal :: $nam: ident),*) => {
+ proto_enum_with_type!(u8, from_byte, as_byte, "{}(0x{:02x})", $typname, $(($bval, $nam)),*);
+ }
+}
+
+#[macro_export]
+macro_rules! proto_varint_enum {
+ ($typname: ident, $($bval: literal :: $nam: ident),*) => {
+ proto_enum_with_type!(VarInt, from_varint, as_varint, "{}({:?})", $typname, $(($bval, $nam)),*);
+ }
+}
+
+#[macro_export]
+macro_rules! proto_int_enum {
+ ($typname: ident, $($bval: literal :: $nam: ident),*) => {
+ proto_enum_with_type!(i32, from_int, as_int, "{}(0x{:02x})", $typname, $(($bval, $nam)),*);
+ }
+}
+
+#[macro_export]
+macro_rules! proto_byte_flag {
+ ($typname: ident, $($bval: literal :: $nam: ident),*) => {
+ #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
+ pub struct $typname(pub u8);
+
+ impl $typname {
+ $(paste::paste! {
+ pub fn [<is_ $nam>](&self) -> bool {
+ self.0 & $bval != 0
+ }
+ })*
+
+ $(paste::paste! {
+ pub fn [<set_ $nam>](&mut self, value: bool) {
+ if value {
+ self.0 |= $bval;
+ } else {
+ self.0 ^= $bval;
+ }
+ }
+ })*
+
+ $(paste::paste! {
+ pub fn [<with_ $nam>](mut self, value: bool) -> Self {
+ if value {
+ self.0 |= $bval;
+ } else {
+ self.0 ^= $bval;
+ }
+
+ self
+ }
+ })*
+ }
+
+ impl Serialize for $typname {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte(self.0)
+ }
+ }
+
+ impl Deserialize for $typname {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(u8::mc_deserialize(data)?.map(move |b| $typname(b)))
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/serialize.rs b/src/serialize.rs
new file mode 100644
index 0000000..5eb8f2a
--- /dev/null
+++ b/src/serialize.rs
@@ -0,0 +1,24 @@
+#[derive(Debug)]
+pub enum SerializeErr {
+ FailedJsonEncode(String),
+ InconsistentPlayerActions(String)
+}
+
+pub type SerializeResult = Result<(), SerializeErr>;
+
+pub trait Serialize: Sized {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult;
+}
+
+pub trait Serializer: Sized {
+
+ fn serialize_bytes(&mut self, data: &[u8]) -> SerializeResult;
+
+ fn serialize_byte(&mut self, byte: u8) -> SerializeResult {
+ self.serialize_bytes(vec!(byte).as_slice())
+ }
+
+ fn serialize_other<S: Serialize>(&mut self, other: &S) -> SerializeResult {
+ other.mc_serialize(self)
+ }
+} \ No newline at end of file
diff --git a/src/status.rs b/src/status.rs
new file mode 100644
index 0000000..b8f9029
--- /dev/null
+++ b/src/status.rs
@@ -0,0 +1,115 @@
+use crate::types::Chat;
+use crate::{SerializeResult, SerializeErr, Serialize as McSerialize, Deserialize as McDeserialize, DeserializeResult, DeserializeErr};
+use crate::uuid::UUID4;
+use serde::{Serialize, Serializer, Deserialize, Deserializer};
+use std::fmt;
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct StatusSpec {
+ pub version: StatusVersionSpec,
+ pub players: StatusPlayersSpec,
+ pub description: Chat,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub favicon: Option<StatusFaviconSpec>,
+}
+
+impl McSerialize for StatusSpec {
+ fn mc_serialize<S: crate::Serializer>(&self, to: &mut S) -> SerializeResult {
+ serde_json::to_string(self).map_err(move |err| {
+ SerializeErr::FailedJsonEncode(format!("failed to serialize json status {}", err))
+ })?.mc_serialize(to)
+ }
+}
+
+impl McDeserialize for StatusSpec {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ String::mc_deserialize(data)?.try_map(move |v| {
+ serde_json::from_str(v.as_str()).map_err(move |err| {
+ DeserializeErr::CannotUnderstandValue(format!("failed to deserialize json status {}", err))
+ })
+ })
+ }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct StatusVersionSpec {
+ pub name: String,
+ pub protocol: i32,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct StatusPlayersSpec {
+ pub max: i32,
+ pub online: i32,
+ #[serde(skip_serializing_if = "Vec::is_empty")]
+ #[serde(default = "Vec::default")]
+ pub sample: Vec<StatusPlayerSampleSpec>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct StatusPlayerSampleSpec {
+ pub name: String,
+ pub id: UUID4,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct StatusFaviconSpec {
+ pub content_type: String,
+ pub data: Vec<u8>,
+}
+
+impl Serialize for StatusFaviconSpec {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+ S: Serializer
+ {
+ let data_base64 = base64::encode(self.data.as_slice());
+ let content = format!("data:{};base64,{}", self.content_type, data_base64);
+ serializer.serialize_str(content.as_str())
+ }
+}
+
+impl<'de> Deserialize<'de> for StatusFaviconSpec {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
+ D: Deserializer<'de>
+ {
+ struct Visitor;
+ impl serde::de::Visitor<'_> for Visitor {
+ type Value = StatusFaviconSpec;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "a string with base64 data for favicon")
+ }
+
+ fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
+ use lazy_static::lazy_static;
+ use regex::Regex;
+ // regex to parse valid base64 content
+ const PATTERN: &str = r"^data:([A-Za-z/]+);base64,([-A-Za-z0-9+/]*={0,3})$";
+ lazy_static! {
+ static ref RE: Regex = Regex::new(PATTERN).expect("regex is valid");
+ }
+
+ // try to use regex on the input
+ // RE.captures_iter(v).next() means "try to get the first capture iterator"
+ // then we take that iterator, get(1), and if 1 exists, get(2), and if both exist,
+ // then we try to parse the base64, and drop the error if one occurs. We then
+ // wrap the content_type and parsed data in StatusFaviconSpec
+ // then we convert the option to a result using map and unwrap_or_else
+ let mut captures: regex::CaptureMatches<'_, '_> = RE.captures_iter(v);
+ captures.next().and_then(move |captures|
+ captures.get(1).and_then(move |content_type|
+ captures.get(2).and_then(move |raw_base64|
+ base64::decode(raw_base64.as_str().as_bytes()).map(move |data| {
+ StatusFaviconSpec {
+ content_type: content_type.as_str().to_owned(),
+ data,
+ }
+ }).ok())))
+ .map(move |result| Ok(result))
+ .unwrap_or_else(|| Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self)))
+ }
+ }
+
+ deserializer.deserialize_str(Visitor {})
+ }
+} \ No newline at end of file
diff --git a/src/testdata/bigtest.nbt b/src/testdata/bigtest.nbt
new file mode 100644
index 0000000..dc3769b
--- /dev/null
+++ b/src/testdata/bigtest.nbt
Binary files differ
diff --git a/src/types.rs b/src/types.rs
new file mode 100644
index 0000000..f62ede9
--- /dev/null
+++ b/src/types.rs
@@ -0,0 +1,1020 @@
+// ... PRIMITIVE TYPES ...
+
+use crate::*;
+use crate::utils::*;
+use crate::uuid::UUID4;
+
+// bool
+impl Serialize for bool {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte(if *self { 1 } else { 0 })
+ }
+}
+
+impl Deserialize for bool {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ read_one_byte(data)?.try_map(move |b| {
+ match b {
+ 0x00 => Ok(false),
+ 0x01 => Ok(true),
+ other => Err(DeserializeErr::InvalidBool(other))
+ }
+ })
+ }
+}
+
+// u8
+impl Serialize for u8 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte(*self)
+ }
+}
+
+impl Deserialize for u8 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ read_one_byte(data)
+ }
+}
+
+// i8
+impl Serialize for i8 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte(*self as u8)
+ }
+}
+
+impl Deserialize for i8 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(read_one_byte(data)?.map(move |byte| byte as i8))
+ }
+}
+
+// u16
+impl Serialize for u16 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let data = write_short(*self);
+ to.serialize_bytes(&data[..])
+ }
+}
+
+impl Deserialize for u16 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ read_short(data)
+ }
+}
+
+// i16
+impl Serialize for i16 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ (*self as u16).mc_serialize(to)
+ }
+}
+
+impl Deserialize for i16 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ u16::mc_deserialize(data)?.map(move |other| other as i16).into()
+ }
+}
+
+// int
+impl Serialize for i32 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let data = write_int(*self as u32);
+ to.serialize_bytes(&data[..])
+ }
+}
+
+impl Deserialize for i32 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(read_int(data)?.map(move |v| v as i32))
+ }
+}
+
+// long
+impl Serialize for i64 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let data = write_long(*self as u64);
+ to.serialize_bytes(&data[..])
+ }
+}
+
+impl Deserialize for i64 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(read_long(data)?.map(move |v| v as i64))
+ }
+}
+
+// float
+impl Serialize for f32 {
+
+ //noinspection ALL
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let data = (*self).to_be_bytes();
+ to.serialize_bytes(&data[..])
+ }
+}
+
+impl Deserialize for f32 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ i32::mc_deserialize(data)?.map(move |r| f32::from_bits(r as u32)).into()
+ }
+}
+
+// double
+impl Serialize for f64 {
+ //noinspection ALL
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let data = (*self).to_be_bytes();
+ to.serialize_bytes(&data[..])
+ }
+}
+
+impl Deserialize for f64 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ i64::mc_deserialize(data)?.map(move |r| f64::from_bits(r as u64)).into()
+ }
+}
+
+// VAR INT AND VAR LONG
+const VAR_INT_BYTES: usize = 5;
+const VAR_LONG_BYTES: usize = 10;
+
+const DESERIALIZE_VAR_INT: impl for<'b> Fn(&'b [u8]) -> DeserializeResult<'b, u64> = deserialize_var_num(VAR_INT_BYTES);
+const DESERIALIZE_VAR_LONG: impl for<'b> Fn(&'b [u8]) -> DeserializeResult<'b, u64> = deserialize_var_num(VAR_LONG_BYTES);
+
+#[derive(Copy, Clone, PartialOrd, PartialEq, Debug, Default, Hash, Ord, Eq)]
+pub struct VarInt(pub i32);
+
+impl Serialize for VarInt {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let mut data = [0u8; VAR_INT_BYTES];
+ to.serialize_bytes(serialize_var_num((self.0 as u32) as u64, &mut data))
+ }
+}
+
+impl Deserialize for VarInt {
+ fn mc_deserialize(orig_data: &[u8]) -> DeserializeResult<Self> {
+ Ok(DESERIALIZE_VAR_INT(orig_data)?.map(move |v| VarInt(v as i32)))
+ }
+}
+
+impl Into<i32> for VarInt {
+ fn into(self) -> i32 {
+ self.0
+ }
+}
+
+impl From<i32> for VarInt {
+ fn from(v: i32) -> Self {
+ Self(v)
+ }
+}
+
+impl Into<usize> for VarInt {
+ fn into(self) -> usize {
+ self.0 as usize
+ }
+}
+
+impl From<usize> for VarInt {
+ fn from(v: usize) -> Self {
+ Self(v as i32)
+ }
+}
+
+impl std::fmt::Display for VarInt {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "VarInt({})", self.0)
+ }
+}
+
+#[derive(Copy, Clone, PartialOrd, PartialEq, Debug, Default, Hash, Ord, Eq)]
+pub struct VarLong(pub i64);
+
+impl Serialize for VarLong {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let mut data = [0u8; VAR_LONG_BYTES];
+ to.serialize_bytes(serialize_var_num(self.0 as u64, &mut data))
+ }
+}
+
+impl Deserialize for VarLong {
+ fn mc_deserialize(orig_data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(DESERIALIZE_VAR_LONG(orig_data)?.map(move |v| VarLong(v as i64)))
+ }
+}
+
+fn serialize_var_num(data: u64, out: &mut [u8]) -> &[u8] {
+ let mut v: u64 = data;
+ let mut byte_idx = 0;
+ let mut has_more = true;
+ while has_more {
+ if byte_idx == out.len() {
+ panic!("tried to write too much data for Var num");
+ }
+
+ let mut v_byte = (v & 0x7F) as u8;
+ v >>= 7;
+ has_more = v != 0;
+ if has_more {
+ v_byte |= 0x80;
+ }
+
+ out[byte_idx] = v_byte;
+ byte_idx += 1;
+ }
+
+ &out[..byte_idx]
+}
+
+const fn deserialize_var_num(max_bytes: usize) -> impl for<'b> Fn(&'b [u8]) -> DeserializeResult<'b, u64> {
+ move |orig_data| {
+ let mut data = orig_data;
+ let mut v: u64 = 0;
+ let mut bit_place: usize = 0;
+ let mut i: usize = 0;
+ let mut has_more = true;
+
+ while has_more {
+ if i == max_bytes {
+ return DeserializeErr::VarNumTooLong(Vec::from(&orig_data[..i])).into();
+ }
+ let Deserialized { value: byte, data: rest } = read_one_byte(data)?;
+ data = rest;
+ has_more = byte & 0x80 != 0;
+ v |= ((byte as u64) & 0x7F) << bit_place;
+ bit_place += 7;
+ i += 1;
+ }
+
+ Deserialized::ok(v, data)
+ }
+}
+
+// STRING
+impl Serialize for String {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&VarInt(self.len() as i32))?;
+ to.serialize_bytes(self.as_bytes())
+ }
+}
+
+impl Deserialize for String {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ VarInt::mc_deserialize(data)?.and_then(move |length, rest| {
+ if length.0 < 0 {
+ Err(DeserializeErr::NegativeLength(length))
+ } else {
+ take(length.0 as usize)(rest)?.try_map(move |taken| {
+ String::from_utf8(taken.to_vec())
+ .map_err(DeserializeErr::BadStringEncoding)
+ })
+ }
+ })
+ }
+}
+
+// position
+#[derive(Clone, Copy, PartialEq, Hash, Debug)]
+pub struct IntPosition {
+ pub x: i32,
+ pub y: i16,
+ pub z: i32,
+}
+
+impl Serialize for IntPosition {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let x_raw = if self.x < 0 {
+ (self.x + 0x2000000) as u64 | 0x2000000
+ } else {
+ self.x as u64
+ } & 0x3FFFFFF;
+ let z_raw = if self.z < 0 {
+ (self.z + 0x2000000) as u64 | 0x2000000
+ } else {
+ self.z as u64
+ } & 0x3FFFFFF;
+ let y_raw = if self.y < 0 {
+ (self.y + 0x800) as u64 | 0x800
+ } else {
+ self.y as u64
+ } & 0xFFF;
+
+ let data_raw = ((x_raw << 38) | (z_raw << 12) | y_raw) as u64;
+ let data_i64 = data_raw as i64;
+ to.serialize_other(&data_i64)
+ }
+}
+
+impl Deserialize for IntPosition {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized{ value: raw, data } = i64::mc_deserialize(data)?;
+ let raw_unsigned = raw as u64;
+ let mut x = ((raw_unsigned >> 38) as u32) & 0x3FFFFFF;
+ let mut z = ((raw_unsigned >> 12) & 0x3FFFFFF) as u32;
+ let mut y = ((raw_unsigned & 0xFFF) as u16) & 0xFFF;
+
+ if (x & 0x2000000) != 0 { // is the 26th bit set
+ // if so, treat the rest as a positive integer, and treat 26th bit as -2^25
+ // 2^25 == 0x2000000
+ // 0x1FFFFFF == 2^26 - 1 (all places set to 1 except 26th place)
+ x = (((x & 0x1FFFFFF) as i32) - 0x2000000) as u32;
+ }
+ if (y & 0x800) != 0 {
+ y = (((y & 0x7FF) as i16) - 0x800) as u16;
+ }
+ if (z & 0x2000000) != 0 {
+ z = (((z & 0x1FFFFFF) as i32) - 0x2000000) as u32;
+ }
+
+ Deserialized::ok(IntPosition{
+ x: x as i32,
+ y: y as i16,
+ z: z as i32
+ }, data)
+ }
+}
+
+// angle
+#[derive(Copy, Clone, PartialEq, Hash, Debug)]
+pub struct Angle {
+ pub value: u8
+}
+
+impl Serialize for Angle {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte(self.value)
+ }
+}
+
+impl Deserialize for Angle {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(read_one_byte(data)?.map(move |b| {
+ Angle { value: b }
+ }))
+ }
+}
+
+// UUID
+
+impl Serialize for UUID4 {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let bytes = self.to_u128().to_be_bytes();
+ to.serialize_bytes(&bytes[..])
+ }
+}
+
+impl Deserialize for UUID4 {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ take(16)(data)?.map(move |bytes| {
+ let raw = (bytes[0] as u128) << 120 |
+ (bytes[1] as u128) << 112 |
+ (bytes[2] as u128) << 104 |
+ (bytes[3] as u128) << 96 |
+ (bytes[4] as u128) << 88 |
+ (bytes[5] as u128) << 80 |
+ (bytes[6] as u128) << 72 |
+ (bytes[7] as u128) << 64 |
+ (bytes[8] as u128) << 56 |
+ (bytes[9] as u128) << 48 |
+ (bytes[10] as u128) << 40 |
+ (bytes[11] as u128) << 32 |
+ (bytes[12] as u128) << 24 |
+ (bytes[13] as u128) << 16 |
+ (bytes[14] as u128) << 8 |
+ bytes[15] as u128;
+ UUID4::from(raw)
+ }).into()
+ }
+}
+
+// NBT
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct NamedNbtTag {
+ pub root: nbt::NamedTag
+}
+
+impl Serialize for NamedNbtTag {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let bytes = self.root.bytes();
+ to.serialize_bytes(bytes.as_slice())
+ }
+}
+
+impl Deserialize for NamedNbtTag {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(nbt::NamedTag::root_compound_tag_from_bytes(data)?.map(move |root| NamedNbtTag { root }))
+ }
+}
+
+impl From<nbt::NamedTag> for NamedNbtTag {
+ fn from(root: nbt::NamedTag) -> Self {
+ Self { root }
+ }
+}
+
+impl Into<nbt::NamedTag> for NamedNbtTag {
+ fn into(self) -> nbt::NamedTag {
+ self.root
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct FixedInt {
+ raw: i32
+}
+
+impl Serialize for FixedInt {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&self.raw)
+ }
+}
+
+impl Deserialize for FixedInt {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(i32::mc_deserialize(data)?.map(move |raw| {
+ FixedInt{ raw }
+ }))
+ }
+}
+
+impl FixedInt {
+ pub fn new(data: f64, fractional_bytes: usize) -> Self {
+ Self { raw: (data * ((1 << fractional_bytes) as f64)) as i32 }
+ }
+
+ pub fn into_float(self, fractional_bytes: usize) -> f64 {
+ (self.raw as f64) / ((1 << fractional_bytes) as f64)
+ }
+}
+
+// chat
+#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
+pub struct Chat {
+ pub text: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub bold: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub italic: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub underlined: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub strikethrough: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub obfuscated: Option<bool>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub color: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub extra: Option<Vec<Chat>>
+}
+
+impl ToString for Chat {
+ fn to_string(&self) -> String {
+ self.extra.as_ref()
+ .into_iter()
+ .flat_map(|v| v.into_iter())
+ .map(|item| item.to_string())
+ .fold(self.text.clone(), |acc, v| acc + v.as_str())
+ }
+}
+
+const SECTION_SYMBOL: char = '§';
+
+#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
+pub enum ColorCode {
+ Black,
+ DarkBlue,
+ DarkGreen,
+ DarkAqua,
+ DarkRed,
+ DarkPurple,
+ Gold,
+ Gray,
+ DarkGray,
+ Blue,
+ Green,
+ Aqua,
+ Red,
+ LightPurple,
+ Yellow,
+ White
+}
+
+impl ColorCode {
+ pub fn from_code(i: &char) -> Option<Self> {
+ match i {
+ '0' => Some(ColorCode::Black),
+ '1' => Some(ColorCode::DarkBlue),
+ '2' => Some(ColorCode::DarkGreen),
+ '3' => Some(ColorCode::DarkAqua),
+ '4' => Some(ColorCode::DarkRed),
+ '5' => Some(ColorCode::DarkPurple),
+ '6' => Some(ColorCode::Gold),
+ '7' => Some(ColorCode::Gray),
+ '8' => Some(ColorCode::DarkGray),
+ '9' => Some(ColorCode::Blue),
+ 'a' => Some(ColorCode::Green),
+ 'b' => Some(ColorCode::Aqua),
+ 'c' => Some(ColorCode::Red),
+ 'd' => Some(ColorCode::LightPurple),
+ 'e' => Some(ColorCode::Yellow),
+ 'f' => Some(ColorCode::White),
+ _ => None
+ }
+ }
+
+ pub fn to_code(&self) -> char {
+ match self {
+ ColorCode::Black => '0',
+ ColorCode::DarkBlue => '1',
+ ColorCode::DarkGreen => '2',
+ ColorCode::DarkAqua => '3',
+ ColorCode::DarkRed => '4',
+ ColorCode::DarkPurple => '5',
+ ColorCode::Gold => '6',
+ ColorCode::Gray => '7',
+ ColorCode::DarkGray => '8',
+ ColorCode::Blue => '9',
+ ColorCode::Green => 'a',
+ ColorCode::Aqua => 'b',
+ ColorCode::Red => 'c',
+ ColorCode::LightPurple => 'd',
+ ColorCode::Yellow => 'e',
+ ColorCode::White => 'f',
+ }
+ }
+
+ pub fn from_name(name: &str) -> Option<Self> {
+ match name.to_ascii_lowercase().as_str() {
+ "black" => Some(ColorCode::Black),
+ "dark_blue" => Some(ColorCode::DarkBlue),
+ "dark_green" => Some(ColorCode::DarkGreen),
+ "dark_aqua" => Some(ColorCode::DarkAqua),
+ "dark_red" => Some(ColorCode::DarkRed),
+ "dark_purple" => Some(ColorCode::DarkPurple),
+ "gold" => Some(ColorCode::Gold),
+ "gray" => Some(ColorCode::Gray),
+ "dark_gray" => Some(ColorCode::DarkGray),
+ "blue" => Some(ColorCode::Blue),
+ "green" => Some(ColorCode::Green),
+ "aqua" => Some(ColorCode::Aqua),
+ "red" => Some(ColorCode::Red),
+ "light_purple" => Some(ColorCode::LightPurple),
+ "yellow" => Some(ColorCode::Yellow),
+ "white" => Some(ColorCode::White),
+ _ => None
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match self {
+ ColorCode::Black => "black",
+ ColorCode::DarkBlue => "dark_blue",
+ ColorCode::DarkGreen => "dark_green",
+ ColorCode::DarkAqua => "dark_aqua",
+ ColorCode::DarkRed => "dark_red",
+ ColorCode::DarkPurple => "dark_purple",
+ ColorCode::Gold => "gold",
+ ColorCode::Gray => "gray",
+ ColorCode::DarkGray => "dark_gray",
+ ColorCode::Blue => "blue",
+ ColorCode::Green => "green",
+ ColorCode::Aqua => "aqua",
+ ColorCode::Red => "red",
+ ColorCode::LightPurple => "light_purple",
+ ColorCode::Yellow => "yellow",
+ ColorCode::White => "white",
+ }
+ }
+}
+
+#[derive(PartialOrd, PartialEq, Debug, Copy, Clone)]
+pub enum Formatter {
+ Color(ColorCode),
+ Obfuscated,
+ Bold,
+ Strikethrough,
+ Underline,
+ Italic,
+ Reset
+}
+
+impl Formatter {
+ pub fn from_code(i: &char) -> Option<Self> {
+ match i.to_ascii_lowercase() {
+ 'k' => Some(Formatter::Obfuscated),
+ 'l' => Some(Formatter::Bold),
+ 'm' => Some(Formatter::Strikethrough),
+ 'n' => Some(Formatter::Underline),
+ 'o' => Some(Formatter::Italic),
+ 'r' => Some(Formatter::Reset),
+ _ => ColorCode::from_code(i).map(Formatter::Color)
+ }
+ }
+
+ pub fn code(&self) -> char {
+ match self {
+ Formatter::Color(c) => c.to_code(),
+ Formatter::Obfuscated => 'k',
+ Formatter::Bold => 'l',
+ Formatter::Strikethrough => 'm',
+ Formatter::Underline => 'n',
+ Formatter::Italic => 'o',
+ Formatter::Reset => 'r'
+ }
+ }
+
+ pub fn from_name(name: &str) -> Option<Self> {
+ match name.to_ascii_lowercase().as_str() {
+ "obfuscated" => Some(Formatter::Obfuscated),
+ "bold" => Some(Formatter::Bold),
+ "strikethrough" => Some(Formatter::Strikethrough),
+ "underline" => Some(Formatter::Underline),
+ "italic" => Some(Formatter::Italic),
+ "reset" => Some(Formatter::Reset),
+ _ => ColorCode::from_name(name).map(Formatter::Color)
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ match self {
+ Formatter::Obfuscated => "obfuscated",
+ Formatter::Bold => "bold",
+ Formatter::Strikethrough => "strikethrough",
+ Formatter::Underline => "underline",
+ Formatter::Italic => "italic",
+ Formatter::Reset => "reset",
+ Formatter::Color(c) => c.name(),
+ }
+ }
+}
+
+impl ToString for Formatter {
+ fn to_string(&self) -> String {
+ vec!(SECTION_SYMBOL, self.code()).into_iter().collect()
+ }
+}
+
+impl Chat {
+ pub fn to_traditional(&self) -> String {
+ self.to_traditional_parts(Vec::<Formatter>::new().as_ref(), None)
+ }
+
+ fn to_traditional_parts(&self, formatters: &Vec<Formatter>, color: Option<ColorCode>) -> String {
+ let mut own_formatters = formatters.clone();
+ Self::update_formatter(&mut own_formatters, Formatter::Bold, &self.bold);
+ Self::update_formatter(&mut own_formatters, Formatter::Italic, &self.italic);
+ Self::update_formatter(&mut own_formatters, Formatter::Underline, &self.underlined);
+ Self::update_formatter(&mut own_formatters, Formatter::Strikethrough, &self.strikethrough);
+ Self::update_formatter(&mut own_formatters, Formatter::Obfuscated, &self.obfuscated);
+
+ let own_color_option = self.color.as_ref()
+ .map(String::as_str)
+ .and_then(ColorCode::from_name)
+ .or(color);
+
+ let own_color = own_color_option
+ .map(Formatter::Color)
+ .map(|f| f.to_string());
+
+ let own_formatter =
+ own_formatters
+ .clone()
+ .into_iter()
+ .map(|f| f.to_string())
+ .fold(String::new(), |acc, v| acc + v.as_str());
+
+ let own_color_str = match own_color {
+ Some(v) => v,
+ None => String::new()
+ };
+
+ let own_out = own_formatter + own_color_str.as_str() + self.text.as_str();
+
+ self.extra.as_ref()
+ .into_iter()
+ .flat_map(|v| v.into_iter())
+ .map(|child| child.to_traditional_parts(&own_formatters, own_color_option))
+ .fold(own_out, |acc, next| acc + next.as_str())
+ }
+
+ fn update_formatter(to: &mut Vec<Formatter>, formatter: Formatter, v: &Option<bool>) {
+ if !to.contains(&formatter) && v.unwrap_or(false) {
+ to.push(formatter)
+ }
+ }
+}
+
+impl Serialize for Chat {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ serde_json::to_string(self).map_err(move |err| {
+ SerializeErr::FailedJsonEncode(format!("failed to serialize chat {:?}", err))
+ })?.mc_serialize(to)
+ }
+}
+
+impl Deserialize for Chat {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ String::mc_deserialize(data)?.try_map(move |str| {
+ serde_json::from_str(str.as_str()).map_err(move |err| {
+ DeserializeErr::FailedJsonDeserialize(format!("failed to deserialize chat {:?}", err))
+ })
+ })
+ }
+}
+
+#[derive(Default)]
+pub struct BytesSerializer {
+ data: Vec<u8>
+}
+
+impl Serializer for BytesSerializer {
+ fn serialize_bytes(&mut self, data: &[u8]) -> SerializeResult {
+ self.data.extend_from_slice(data);
+ Ok(())
+ }
+}
+
+impl BytesSerializer {
+ pub fn with_capacity(cap: usize) -> Self {
+ BytesSerializer{
+ data: Vec::with_capacity(cap),
+ }
+ }
+
+ pub fn into_bytes(self) -> Vec<u8> {
+ self.data
+ }
+}
+
+impl<T> Serialize for Option<T> where T: Serialize {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ match self {
+ Some(value) => {
+ to.serialize_other(&true)?;
+ to.serialize_other(value)
+ },
+ None => {
+ to.serialize_other(&false)
+ }
+ }
+ }
+}
+
+impl<T> Deserialize for Option<T> where T: Deserialize {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ bool::mc_deserialize(data)?.and_then(move |is_present, data| {
+ if is_present {
+ Ok(T::mc_deserialize(data)?.map(move |component| Some(component)))
+ } else {
+ Deserialized::ok(None, data)
+ }
+ })
+ }
+}
+
+// SLOT
+#[derive(Debug, PartialEq, Clone)]
+pub struct Slot {
+ pub item_id: VarInt,
+ pub item_count: i8,
+ pub nbt: Option<nbt::NamedTag>,
+}
+
+impl Serialize for Slot {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&self.item_id)?;
+ to.serialize_other(&self.item_count)?;
+ match self.nbt.as_ref() {
+ Some(nbt) => to.serialize_bytes(nbt.bytes().as_slice()),
+ None => to.serialize_byte(nbt::Tag::End.id()),
+ }
+ }
+}
+
+impl Deserialize for Slot {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized{ value: item_id, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized{ value: item_count, data } = i8::mc_deserialize(data)?;
+ if data.is_empty() {
+ return Err(DeserializeErr::Eof);
+ }
+
+ let id = data[0];
+ let rest = &data[1..];
+ Ok(match id {
+ 0x00 => Deserialized{ value: None, data: rest },
+ _ => nbt::read_named_tag(data)?.map(move |tag| Some(tag))
+ }.map(move |nbt| {
+ Slot{ item_id, item_count, nbt }
+ }))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fmt::Debug;
+
+ #[test]
+ fn test_bool() {
+ test_type(true);
+ test_type(false);
+ }
+
+ #[test]
+ fn test_signed_byte() {
+ test_type(0i8);
+ test_type(127i8);
+ test_type(-15i8);
+ }
+
+ #[test]
+ fn test_unsigned_byte() {
+ test_type(0u8);
+ test_type(128u8);
+ test_type(255u8);
+ }
+
+ #[test]
+ fn test_signed_short() {
+ test_type(0i16);
+ test_type(-88i16);
+ test_type(25521i16);
+ }
+
+ #[test]
+ fn test_unsigned_short() {
+ test_type(0u16);
+ test_type(1723u16);
+ test_type(65534u16);
+ }
+
+ #[test]
+ fn test_signed_int() {
+ test_type(0i32);
+ test_type(123127i32);
+ test_type(-171238i32);
+ test_type(2147483647i32);
+ }
+
+ #[test]
+ fn test_signed_long() {
+ test_type(0i64);
+ test_type(123127i64);
+ test_type(-12123127i64);
+ test_type(2147483647i64);
+ test_type(-10170482028482i64);
+ }
+
+ #[test]
+ fn test_float() {
+ test_type(0.2313f32);
+ test_type(0f32);
+ test_type(123123213f32);
+ test_type(-123123f32);
+ }
+
+ #[test]
+ fn test_double() {
+ test_type(0.2313f64);
+ test_type(0f64);
+ test_type(123123213f64);
+ test_type(-123123f64);
+ }
+
+ #[test]
+ fn test_var_int() {
+ test_type(VarInt(0));
+ test_type(VarInt(1231231));
+ test_type(VarInt(2147483647));
+ test_type(VarInt(-2147483648));
+ test_type(VarInt(-1));
+ test_type(VarInt(-1001237));
+ }
+
+ #[test]
+ fn test_var_long() {
+ test_type(VarLong(0));
+ test_type(VarLong(1231231));
+ test_type(VarLong(12312319123));
+ test_type(VarLong(9223372036854775807));
+ test_type(VarLong(-1));
+ test_type(VarLong(-12312319123));
+ test_type(VarLong(-9223372036854775808));
+ test_type(VarLong(-1001237));
+ }
+
+ #[test]
+ fn test_string() {
+ test_type(String::from("hello my name is joey 123"));
+ test_type(String::from(""));
+ test_type(String::from("AAAA"));
+ test_type(String::from("hello my name is joey 123").repeat(1000));
+ }
+
+ #[test]
+ fn test_nbt() {
+ test_type(NamedNbtTag {root: nbt::Tag::Compound(vec!(
+ nbt::Tag::String("test 123".to_owned()).with_name("abc 123")
+ )).with_name("root")})
+ }
+
+ #[test]
+ fn test_int_position() {
+ test_type(IntPosition{
+ x: 12312,
+ y: -32,
+ z: 321312,
+ });
+
+ test_type(IntPosition{
+ x: 12312,
+ y: -32,
+ z: -321312,
+ });
+
+ test_type(IntPosition{
+ x: -12312,
+ y: -32,
+ z: -321312,
+ });
+
+ test_type(IntPosition{
+ x: -12312,
+ y: 32,
+ z: 321312,
+ });
+
+ test_type(IntPosition{
+ x: 0,
+ y: 0,
+ z: 0,
+ });
+
+ test_type(IntPosition{
+ x: 48,
+ y: 232,
+ z: 12,
+ });
+
+ test_type(IntPosition{
+ x: 33554431,
+ y: 2047,
+ z: 33554431,
+ });
+
+ test_type(IntPosition{
+ x: -33554432,
+ y: -2048,
+ z: -33554432,
+ });
+
+ test_type(IntPosition{
+ x: 3,
+ y: 0,
+ z: 110655,
+ });
+ }
+
+ #[test]
+ fn test_uuid() {
+ for _ in 0..5 {
+ test_type(UUID4::random());
+ }
+ }
+
+ #[test]
+ fn test_angle() {
+ test_type(Angle{
+ value: 0,
+ });
+ test_type(Angle{
+ value: 24,
+ });
+ test_type(Angle{
+ value: 255,
+ });
+ test_type(Angle{
+ value: 8,
+ });
+ }
+
+ fn test_type<S: Serialize + Deserialize + PartialEq + Debug>(value: S) {
+ let bytes = {
+ let mut test = BytesSerializer::default();
+ value.mc_serialize(&mut test).expect("serialization should succeed");
+ test.into_bytes()
+ };
+ let deserialized = S::mc_deserialize(bytes.as_slice()).expect("deserialization should succeed");
+ assert!(deserialized.data.is_empty());
+ assert_eq!(deserialized.value, value, "deserialized value == serialized value");
+ let re_serialized = {
+ let mut test = BytesSerializer::default();
+ deserialized.value.mc_serialize(&mut test).expect("serialization should succeed");
+ test.into_bytes()
+ };
+ assert_eq!(re_serialized, bytes, "serialized value == original serialized bytes");
+ }
+} \ No newline at end of file
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..7371d1c
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,132 @@
+use crate::{DeserializeResult, DeserializeErr, Deserialized};
+
+#[inline]
+pub fn read_one_byte(data: &[u8]) -> DeserializeResult<u8> {
+ match data.split_first() {
+ Some((byte, rest)) => Deserialized::ok(*byte, rest),
+ None => Err(DeserializeErr::Eof)
+ }
+}
+
+#[inline]
+pub const fn take(amount: usize) -> impl for<'b> Fn(&'b [u8]) -> DeserializeResult<'b, &'b [u8]> {
+ move |data| {
+ if data.len() < amount {
+ Err(DeserializeErr::Eof)
+ } else {
+ Ok(data.split_at(amount).into())
+ }
+ }
+}
+
+#[inline]
+pub fn read_long(data: &[u8]) -> DeserializeResult<u64> {
+ Ok(take(8)(data)?.map(move |bytes| {
+ (bytes[0] as u64) << 56 |
+ (bytes[1] as u64) << 48 |
+ (bytes[2] as u64) << 40 |
+ (bytes[3] as u64) << 32 |
+ (bytes[4] as u64) << 24 |
+ (bytes[5] as u64) << 16 |
+ (bytes[6] as u64) << 8 |
+ (bytes[7] as u64)
+ }))
+}
+
+#[inline]
+pub fn write_long(v: u64) -> [u8; 8] {
+ [
+ (v >> 56) as u8,
+ (v >> 48) as u8,
+ (v >> 40) as u8,
+ (v >> 32) as u8,
+ (v >> 24) as u8,
+ (v >> 16) as u8,
+ (v >> 8) as u8,
+ v as u8,
+ ]
+}
+
+#[inline]
+pub fn read_int(data: &[u8]) -> DeserializeResult<u32> {
+ Ok(take(4)(data)?.map(move |bytes| {
+ (bytes[0] as u32) << 24 |
+ (bytes[1] as u32) << 16 |
+ (bytes[2] as u32) << 8 |
+ (bytes[3] as u32)
+ }))
+}
+
+#[inline]
+pub fn write_int(v: u32) -> [u8; 4] {
+ [(v >> 24) as u8, (v >> 16) as u8, (v >> 8) as u8, v as u8]
+}
+
+#[inline]
+pub fn read_short(data: &[u8]) -> DeserializeResult<u16> {
+ Ok(take(2)(data)?.map(move |bytes| {
+ (bytes[0] as u16) << 8 | (bytes[1] as u16)
+ }))
+}
+
+#[inline]
+pub fn write_short(v: u16) -> [u8; 2] {
+ [(v >> 8) as u8, v as u8]
+}
+
+#[inline]
+pub fn hex(data: &[u8]) -> String {
+ let mut str = String::with_capacity(data.len() * 2);
+ for byte_ref in data {
+ let byte = *byte_ref;
+ str.push(hex_char_for(byte >> 4));
+ str.push(hex_char_for(byte & 0xF));
+ }
+ str
+}
+
+const ZERO_ASCII_CODE: u8 = 48;
+const LOWER_A_ASCII_CODE: u8 = 97;
+
+#[inline]
+fn hex_char_for(half: u8) -> char {
+ if half > 0xF {
+ panic!("not defined for > 0xF (operates on half a byte)");
+ }
+
+ if half < 10 {
+ (half + ZERO_ASCII_CODE) as char
+ } else {
+ (half + (LOWER_A_ASCII_CODE - 10)) as char
+ }
+}
+
+#[inline]
+pub fn parse_hex_char(data: u8) -> Option<u8> {
+ const UPPER_A_ASCII_CODE: u8 = 65;
+ const LOWER_F_ASCII_CODE: u8 = 102;
+ const UPPER_F_ASCII_CODE: u8 = 70;
+ const NINE_ASCII_CODE: u8 = 57;
+
+ if data >= LOWER_A_ASCII_CODE {
+ if data > LOWER_F_ASCII_CODE {
+ None
+ } else {
+ Some(10 + (data - LOWER_A_ASCII_CODE))
+ }
+ } else if data >= UPPER_A_ASCII_CODE {
+ if data > UPPER_F_ASCII_CODE {
+ None
+ } else {
+ Some(10 + (data - UPPER_A_ASCII_CODE))
+ }
+ } else if data >= ZERO_ASCII_CODE {
+ if data > NINE_ASCII_CODE {
+ None
+ } else {
+ Some(data - ZERO_ASCII_CODE)
+ }
+ } else {
+ None
+ }
+} \ No newline at end of file
diff --git a/src/uuid.rs b/src/uuid.rs
new file mode 100644
index 0000000..23a4e1b
--- /dev/null
+++ b/src/uuid.rs
@@ -0,0 +1,185 @@
+use std::fmt::{Display, Formatter, Debug};
+use lazy_static::lazy_static;
+use regex::Regex;
+use serde::{Serializer, Deserializer};
+use crate::utils::*;
+
+#[derive(Copy, Clone, PartialEq, Hash, Eq)]
+pub struct UUID4 {
+ raw: u128
+}
+
+impl Display for UUID4 {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.write_str(self.hex().as_str())
+ }
+}
+
+impl Debug for UUID4 {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.write_str("UUID4{")?;
+ f.write_str(self.hex().as_str())?;
+ f.write_str("}")
+ }
+}
+
+impl From<u128> for UUID4 {
+ fn from(raw: u128) -> Self {
+ UUID4 {
+ raw
+ }
+ }
+}
+
+impl serde::Serialize for UUID4 {
+ fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> where
+ S: Serializer {
+ serializer.serialize_str(self.to_string().as_str())
+ }
+}
+
+impl<'de> serde::Deserialize<'de> for UUID4 {
+ fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error> where
+ D: Deserializer<'de> {
+ struct Visitor;
+ impl serde::de::Visitor<'_> for Visitor {
+ type Value = UUID4;
+
+ fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(formatter, "a string representing the UUID")
+ }
+
+ fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
+ if let Some(id) = UUID4::parse(v) {
+ Ok(id)
+ } else {
+ Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self))
+ }
+ }
+ }
+
+ deserializer.deserialize_str(Visitor{})
+ }
+}
+
+impl UUID4 {
+ pub fn parse(from: &str) -> Option<UUID4> {
+ const PATTERN: &str = r"^([A-Fa-f0-9]{8})-?([A-Fa-f0-9]{4})-?([A-Fa-f0-9]{4})-?([A-Fa-f0-9]{4})-?([A-Fa-f0-9]{12})$";
+ // let re = Regex::new(PATTERN).expect("regex is valid");
+ lazy_static! {
+ static ref RE: Regex = Regex::new(PATTERN)
+ .expect("regex is valid");
+ }
+
+ RE.captures_iter(from).filter_map(move |c|
+ c.get(1).map(move |g| g.as_str()).and_then(move |g0|
+ c.get(2).map(move |g| g.as_str()).and_then(move |g1|
+ c.get(3).map(move |g| g.as_str()).and_then(move |g2|
+ c.get(4).map(move |g| g.as_str()).and_then(move |g3|
+ c.get(5).map(move |g4|
+ RawUUID4 { parts: [g0, g1, g2, g3, g4.as_str()] }))))))
+ .nth(0)
+ .and_then(move |raw| raw.parse())
+ }
+
+ pub fn random() -> Self {
+ UUID4 {
+ raw: rand::random(),
+ }
+ }
+
+ pub fn to_u128(self) -> u128 {
+ self.raw
+ }
+
+ pub fn hex(self) -> String {
+ let bytes = self.raw.to_be_bytes();
+ let parts = [
+ hex(&bytes[..4]),
+ hex(&bytes[4..6]),
+ hex(&bytes[6..8]),
+ hex(&bytes[8..10]),
+ hex(&bytes[10..16]),
+ ];
+ parts.join("-")
+ }
+}
+
+struct RawUUID4<'a> {
+ parts: [&'a str; 5],
+}
+
+impl<'a> RawUUID4<'a> {
+ fn parse(self) -> Option<UUID4> {
+ let mut bit_index: usize = 0;
+ let mut raw: u128 = 0;
+
+ for part in &self.parts {
+ for char in part.chars() {
+ if let Some(parsed) = parse_hex_char(char as u8) {
+ raw |= (parsed as u128) << (124 - bit_index);
+ bit_index += 4;
+ } else {
+ return None;
+ }
+ }
+ }
+
+ Some(UUID4 { raw })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::UUID4;
+
+ #[test]
+ fn test_random_uuid4() {
+ UUID4::random();
+ }
+
+ const VALID_UUID: &str = "e1cde35a-0758-47f6-adf8-9dcb44884e5d";
+
+ #[test]
+ fn test_uuid4_parse() {
+ UUID4::parse(VALID_UUID)
+ .expect("should parse valid uuid correctly");
+ }
+
+ #[test]
+ fn test_parsed_uuid4_to_hex() {
+ let uuid_hex =
+ UUID4::parse(VALID_UUID)
+ .expect("should parse valid uuid correctly")
+ .hex();
+
+ assert_eq!(uuid_hex.as_str(), VALID_UUID)
+ }
+
+ #[test]
+ fn test_uuid4_equal() {
+ let uuid_a = UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly");
+ let uuid_b = UUID4::parse(VALID_UUID).expect("should parse valid uuid correctly");
+ assert_eq!(uuid_a, uuid_b);
+ }
+
+ #[test]
+ fn test_random_uuid4_hex() {
+ let src_uuid = UUID4::random();
+ let uuid_hex = src_uuid.hex();
+ let uuid_parsed = UUID4::parse(uuid_hex.as_str()).expect("should parse generated uuid correctly");
+ assert_eq!(src_uuid, uuid_parsed);
+ let uuid_parsed_hex = uuid_parsed.hex();
+ assert_eq!(uuid_hex, uuid_parsed_hex);
+ }
+
+ #[test]
+ fn test_display_uuid() {
+ println!("got uuid {}", UUID4::random());
+ }
+
+ #[test]
+ fn test_debug_uuid() {
+ println!("got uuid {:?}", UUID4::random());
+ }
+} \ No newline at end of file
diff --git a/src/v1_15_2.rs b/src/v1_15_2.rs
new file mode 100644
index 0000000..d2ee960
--- /dev/null
+++ b/src/v1_15_2.rs
@@ -0,0 +1,2391 @@
+use crate::{*, uuid::*, types::*};
+use std::fmt::Debug;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum PacketDirection {
+ ClientBound,
+ ServerBound,
+}
+
+impl PacketDirection {
+ pub fn opposite(&self) -> Self {
+ use PacketDirection::*;
+ match self {
+ ClientBound => ServerBound,
+ ServerBound => ClientBound,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum State {
+ Handshaking,
+ Status,
+ Login,
+ Play,
+}
+
+impl State {
+ pub fn name(&self) -> String {
+ use State::*;
+ match self {
+ Handshaking => "Handshaking",
+ Status => "Status",
+ Login => "Login",
+ Play => "Play",
+ }.to_owned()
+ }
+}
+
+define_protocol!(Packet578, PacketDirection, State, i32, Id => {
+ // handshaking
+ Handshake, 0x00, Handshaking, ServerBound => HandshakeSpec {
+ version: VarInt,
+ server_address: String,
+ server_port: u16,
+ next_state: HandshakeNextState
+ },
+
+ // status
+ StatusRequest, 0x00, Status, ServerBound => StatusRequestSpec {},
+ StatusPing, 0x01, Status, ServerBound => StatusPingSpec {
+ payload: i64
+ },
+ StatusResponse, 0x00, Status, ClientBound => StatusResponseSpec {
+ response: super::status::StatusSpec
+ },
+ StatusPong, 0x01, Status, ClientBound => StatusPongSpec {
+ payload: i64
+ },
+
+ // login
+ LoginDisconnect, 0x00, Login, ClientBound => LoginDisconnectSpec {
+ message: Chat
+ },
+ LoginEncryptionRequest, 0x01, Login, ClientBound => LoginEncryptionRequestSpec {
+ server_id: String,
+ public_key: VarIntCountedArray<u8>,
+ verify_token: VarIntCountedArray<u8>
+ },
+ LoginSuccess, 0x02, Login, ClientBound => LoginSuccessSpec {
+ uuid_string: String,
+ username: String
+ },
+ LoginSetCompression, 0x03, Login, ClientBound => LoginSetCompressionSpec {
+ threshold: VarInt
+ },
+ LoginPluginRequest, 0x04, Login, ClientBound => LoginPluginRequestSpec {
+ message_id: VarInt,
+ channel: String,
+ data: RemainingBytes
+ },
+ LoginStart, 0x00, Login, ServerBound => LoginStartSpec {
+ name: String
+ },
+ LoginEncryptionResponse, 0x01, Login, ServerBound => LoginEncryptionResponseSpec {
+ shared_secret: VarIntCountedArray<u8>,
+ verify_token: VarIntCountedArray<u8>
+ },
+ LoginPluginResponse, 0x02, Login, ServerBound => LoginPluginResponseSpec {
+ message_id: VarInt,
+ successful: bool,
+ data: RemainingBytes
+ },
+
+ // play
+ // client bound
+ PlaySpawnEntity, 0x00, Play, ClientBound => PlaySpawnEntitySpec {
+ entity_id: VarInt,
+ object_uuid: UUID4,
+ entity_type: VarInt,
+ x: f64,
+ y: f64,
+ z: f64,
+ pitch: Angle,
+ yaw: Angle,
+ data: i32,
+ velocity_x: i16,
+ velocity_y: i16,
+ velocity_z: i16
+ },
+ PlaySpawnExperienceOrb, 0x01, Play, ClientBound => PlaySpawnExperienceOrbSpec {
+ entity_id: VarInt,
+ x: f64,
+ y: f64,
+ z: f64,
+ count: i16
+ },
+ PlaySpawnWeatherEntity, 0x02, Play, ClientBound => PlaySpawnWeatherEntitySpec {
+ entity_id: VarInt,
+ entity_type: u8,
+ x: f64,
+ y: f64,
+ z: f64
+ },
+ PlaySpawnLivingEntity, 0x03, Play, ClientBound => PlaySpawnLivingEntitySpec {
+ entity_id: VarInt,
+ entity_uuid: UUID4,
+ entity_type: VarInt,
+ x: f64,
+ y: f64,
+ z: f64,
+ yaw: Angle,
+ pitch: Angle,
+ head_pitch: Angle,
+ velocity_x: i16,
+ velocity_y: i16,
+ velocity_z: i16
+ },
+ PlaySpawnPainting, 0x04, Play, ClientBound => PlaySpawnPaintingSpec {
+ entity_id: VarInt,
+ entity_uuid: UUID4,
+ motive: VarInt,
+ location: IntPosition,
+ direction: CardinalDirection
+ },
+ PlaySpawnPlayer, 0x05, Play, ClientBound => PlaySpawnPlayerSpec {
+ entity_id: VarInt,
+ uuid: UUID4,
+ x: f64,
+ y: f64,
+ z: f64,
+ yaw: Angle,
+ pitch: Angle
+ },
+ PlayEntityAnimation, 0x06, Play, ClientBound => PlayEntityAnimationSpec {
+ entity_id: VarInt,
+ animation: EntityAnimationKind
+ },
+ PlayStatistics, 0x07, Play, ClientBound => PlayStatisticsSpec {
+ entries: VarIntCountedArray<Statistic>
+ },
+ PlayAcknowledgePlayerDigging, 0x08, Play, ClientBound => PlayAcknowledgePlayerDiggingSpec {
+ location: IntPosition,
+ block: VarInt,
+ status: DiggingStatus,
+ successful: bool
+ },
+ PlayBlockBreakAnimation, 0x09, Play, ClientBound => PlayBlockBreakAnimationSpec {
+ entity_id: VarInt,
+ location: IntPosition,
+ destroy_stage: i8
+ },
+ PlayBlockEntityData, 0x0A, Play, ClientBound => PlayBlockEntityDataSpec {
+ location: IntPosition,
+ action: BlockEntityDataAction,
+ nbt_data: NamedNbtTag
+ },
+ PlayBlockAction, 0x0B, Play, ClientBound => PlayBlockActionSpec {
+ location: IntPosition,
+ action_id: u8,
+ action_payload: u8,
+ block_type: VarInt
+ },
+ PlayBlockChange, 0x0C, Play, ClientBound => PlayBlockChangeSpec {
+ location: IntPosition,
+ block_id: VarInt
+ },
+ PlayBossBar, 0x0D, Play, ClientBound => PlayBossBarSpec {
+ uuid: UUID4,
+ action: BossBarAction
+ },
+ PlayServerDifficulty, 0x0E, Play, ClientBound => PlayServerDifficultySpec {
+ difficulty: Difficulty,
+ locked: bool
+ },
+ PlayServerChatMessage, 0x0F, Play, ClientBound => PlayServerChatMessageSpec {
+ message: Chat,
+ position: ChatPosition
+ },
+ PlayMultiBlockChange, 0x10, Play, ClientBound => PlayMultiBlockChangeSpec {
+ chunk_x: i32,
+ chunk_z: i32,
+ changes: VarIntCountedArray<MultiBlockChangeRecord>
+ },
+ PlayTabComplete, 0x11, Play, ClientBound => PlayTabCompleteSpec {
+ id: VarInt,
+ start: VarInt,
+ length: VarInt,
+ matches: VarIntCountedArray<TabCompleteMatch>
+ },
+ // SKIP PlayDeclareCommands
+ PlayDeclareCommands, 0x12, Play, ClientBound => PlayDeclareCommandsSpec {
+ raw_data: RemainingBytes
+ },
+ PlayServerWindowConfirmation, 0x13, Play, ClientBound => PlayServerWindowConfirmationSpec {
+ window_id: u8,
+ action_number: i16,
+ accepted: bool
+ },
+ PlayServerCloseWindow, 0x14, Play, ClientBound => PlayServerCloseWindowSpec {
+ window_id: u8
+ },
+ PlayWindowItems, 0x15, Play, ClientBound => PlayWindowItemsSpec {
+ window_id: u8,
+ slots: ShortCountedArray<Option<Slot>>
+ },
+ PlayWindowProperty, 0x16, Play, ClientBound => PlayWindowPropertySpec {
+ window_id: u8,
+ property: i16,
+ value: i16
+ },
+ PlaySetSlot, 0x17, Play, ClientBound => PlaySetSlotSpec {
+ window_id: u8,
+ slow: i16,
+ slot_data: Option<Slot>
+ },
+ PlaySetCooldown, 0x18, Play, ClientBound => PlaySetCooldownSpec {
+ item_id: VarInt,
+ cooldown_ticks: VarInt
+ },
+ PlayServerPluginMessage, 0x19, Play, ClientBound => PlayServerPluginMessageSpec {
+ channel: String,
+ data: RemainingBytes
+ },
+ PlayNamedSoundEffect, 0x1A, Play, ClientBound => PlayNamedSoundEffectSpec {
+ sound_name: String,
+ sound_category: SoundCategory,
+ position_x: FixedInt,
+ position_y: FixedInt,
+ position_z: FixedInt,
+ volume: f32,
+ pitch: f32
+ },
+ PlayDisconnect, 0x1B, Play, ClientBound => PlayDisconnectSpec {
+ reason: Chat
+ },
+ PlayEntityStatus, 0x1C, Play, ClientBound => PlayEntityStatusSpec {
+ entity_id: VarInt,
+ raw_status: u8 // todo deal with the gigantic table
+ },
+ PlayExposion, 0x1D, Play, ClientBound => PlayExposionSpec {
+ x: f32,
+ y: f32,
+ z: f32,
+ strength: f32,
+ records: IntCountedArray<ExposionRecord>,
+ player_motion_x: f32,
+ player_motion_y: f32,
+ player_motion_z: f32
+ },
+ PlayUnloadChunk, 0x1E, Play, ClientBound => PlayUnloadChunkSpec {
+ x: i32,
+ y: i32
+ },
+ PlayChangeGameState, 0x1F, Play, ClientBound => PlayChangeGameStateSpec {
+ reason: GameChangeReason
+ },
+ PlayOpenHorseWindow, 0x20, Play, ClientBound => PlayOpenHorseWindowSpec {
+ window_id: u8,
+ number_of_slots: VarInt,
+ entity_id: i32
+ },
+ PlayServerKeepAlive, 0x21, Play, ClientBound => PlayServerKeepAliveSpec {
+ id: i64
+ },
+ PlayChunkData, 0x22, Play, ClientBound => PlayChunkDataWrapper {
+ data: ChunkData
+ },
+ PlayEffect, 0x23, Play, ClientBound => PlayEffectSpec {
+ effect_id: i32,
+ location: IntPosition,
+ data: i32,
+ disable_relative_volume: bool
+ },
+ PlayParticle, 0x24, Play, ClientBound => PlayParticleSpec {
+ particle_id: i32,
+ long_distance: bool,
+ x: f64,
+ y: f64,
+ z: f64,
+ offset_x: f32,
+ offset_y: f32,
+ offset_z: f32,
+ particle_data: i32,
+ data: RemainingBytes // todo
+ },
+ PlayUpdateLight, 0x25, Play, ClientBound => PlayUpdateLoightSpec {
+ chunk_x: VarInt,
+ chunk_z: VarInt,
+ sky_light_mask: VarInt,
+ block_light_mask: VarInt,
+ empty_sky_light_mask: VarInt,
+ empty_block_light_mask: VarInt,
+ sky_lights: VarIntCountedArray<u8>,
+ block_lights: VarIntCountedArray<u8>
+ },
+ PlayJoinGame, 0x26, Play, ClientBound => PlayJoinGameSpec {
+ entity_id: i32,
+ gamemode: GameMode,
+ dimension: Dimension,
+ hashed_seed: i64,
+ max_players: u8,
+ level_type: String,
+ view_distance: VarInt,
+ reduced_debug_info: bool,
+ enable_respawn_screen: bool
+ },
+ PlayMapData, 0x27, Play, ClientBound => PlayMapDataSpec {
+ map_id: VarInt,
+ scale: i8,
+ tracking_position: bool,
+ locked: bool,
+ icons: VarIntCountedArray<MapIconSpec>,
+ columns: MapColumns
+ },
+ PlayTradeList, 0x28, Play, ClientBound => PlayTradeListSpec {
+ window_id: VarInt,
+ trades: ByteCountedArray<TradeSpec>,
+ villager_level: VarInt,
+ experience: VarInt,
+ regular_villager: bool,
+ can_restock: bool
+ },
+ PlayEntityPosition, 0x29, Play, ClientBound => PlayEntityPositionSpec {
+ entity_id: VarInt,
+ delta_x: i16,
+ delta_y: i16,
+ delta_z: i16,
+ on_ground: bool
+ },
+ PlayEntityPositionAndRotation, 0x2A, Play, ClientBound => PlayEntityPositionAndRotationSpec {
+ entity_id: VarInt,
+ delta_x: i16,
+ delta_y: i16,
+ delta_z: i16,
+ yaw: Angle,
+ pitch: Angle,
+ on_ground: bool
+ },
+ PlayEntityRotation, 0x2B, Play, ClientBound => PlayEntityRotationSpec {
+ entity_id: VarInt,
+ yaw: Angle,
+ pitch: Angle,
+ on_ground: bool
+ },
+ PlayEntityMovement, 0x2C, Play, ClientBound => PlayEntityMovementSpec {
+ entity_id: VarInt
+ },
+ PlayServerVehicleMove, 0x2D, Play, ClientBound => PlayEntityVehicleMoveSpec {
+ x: f64,
+ y: f64,
+ z: f64,
+ yaw: f32,
+ pitch: f32
+ },
+ PlayOpenBook, 0x2E, Play, ClientBound => PlayOpenBookSpec {
+ hand: Hand
+ },
+ PlayOpenWindow, 0x2F, Play, ClientBound => PlayOpenWindowSpec {
+ id: VarInt,
+ kind: WindowType,
+ title: String
+ },
+ PlayOpenSignEditor, 0x30, Play, ClientBound => PlayOpenSignEditorSpec {
+ location: IntPosition
+ },
+ PlayCraftRecipeResponse, 0x31, Play, ClientBound => PlayCraftRecipeResponseSpec {
+ window_id: u8,
+ recipe: String
+ },
+ PlayServerPlayerAbilities, 0x32, Play, ClientBound => PlayServerPlayerAbilitiesSpec {
+ flags: PlayerAbilityFlags,
+ flying_speed: f32,
+ field_of_view_modifier: f32
+ },
+ PlayCombatEvent, 0x33, Play, ClientBound => PlayCombatEventSpec {
+ event: CombatEvent
+ },
+ PlayPlayerInfo, 0x34, Play, ClientBound => PlayPlayerInfoSpec {
+ actions: PlayerInfoActionList
+ },
+ PlayFacePlayer, 0x35, Play, ClientBound => PlayFacePlayerSpec {
+ face_kind: FacePlayerKind,
+ target_x: f64,
+ target_y: f64,
+ target_z: f64,
+ entity: Option<FacePlayerEntityTarget>
+ },
+ PlayServerPlayerPositionAndLook, 0x36, Play, ClientBound => PlayServerPlayerPositionAndLookSpec {
+ x: f64,
+ y: f64,
+ z: f64,
+ yaw: f32,
+ pitch: f32,
+ flags: PositionAndLookFlags,
+ teleport_id: VarInt
+ },
+ PlayUnlockRecipes, 0x37, Play, ClientBound => PlayUnlockRecipesSpec {
+ action: RecipeUnlockAction,
+ crafting_book_open: bool,
+ crafting_book_active: bool,
+ smelting_book_open: bool,
+ smelting_book_active: bool,
+ recipe_ids: VarIntCountedArray<String>,
+ other_recipe_ids: RemainingBytes // todo
+ },
+ PlayDestroyEntities, 0x38, Play, ClientBound => PlayDestroyEntitiesSpec {
+ entity_ids: VarIntCountedArray<VarInt>
+ },
+ PlayRemoveEntityEffect, 0x39, Play, ClientBound => PlayRemoveEntityEffectSpec {
+ entity_id: VarInt,
+ effect: EntityEffectKind
+ },
+ PlayResourcePackSend, 0x3A, Play, ClientBound => PlayResourcePackSendSpec {
+ url: String,
+ hash: String
+ },
+ PlayRespawn, 0x3B, Play, ClientBound => PlayRespawnSpec {
+ dimension: Dimension,
+ hashed_seed: i64,
+ gamemode: GameMode,
+ level_type: String
+ },
+ PlayEntityHeadLook, 0x3C, Play, ClientBound => PlayEntityHeadLookSpec {
+ entity_id: VarInt,
+ head_yaw: Angle
+ },
+ PlaySelectAdvancementTab, 0x3D, Play, ClientBound => PlaySelectAdvancementTabSpec {
+ identifier: Option<String>
+ },
+ PlayWorldBorder, 0x3E, Play, ClientBound => PlayWorldBorderSpec {
+ action: WorldBorderAction
+ },
+ PlayCamera, 0x3F, Play, ClientBound => PlayCameraSpec {
+ camera_id: VarInt
+ },
+ PlayServerHeldItemChange, 0x40, Play, ClientBound => PlayServerHeldItemChangeSpec {
+ slot: i8
+ },
+ PlayUpdateViewPosition, 0x41, Play, ClientBound => PlayUpdateViewPositionSpec {
+ chunk_x: VarInt,
+ chunk_z: VarInt
+ },
+ PlayUpdateViewDistance, 0x42, Play, ClientBound => PlayUpdateViewDistanceSpec {
+ view_distance: VarInt
+ },
+ PlayDisplayScoreboard, 0x43, Play, ClientBound => PlayDisplayScoreboardSpec {
+ position: ScoreboardPosition,
+ score_name: String
+ },
+ PlayEntityMetadata, 0x44, Play, ClientBound => PlayEntityMetadataSpec {
+ entity_id: VarInt,
+ metadata_raw: RemainingBytes
+ },
+ PlayAttachEntity, 0x45, Play, ClientBound => PlayAttachEntitySpec {
+ attached_entity_id: i32,
+ holding_entity_id: i32
+ },
+ PlayEntityVelocity, 0x46, Play, ClientBound => PlayEntityVelocitySpec {
+ entity_id: VarInt,
+ velocity_x: i16,
+ velocity_y: i16,
+ velocity_z: i16
+ },
+ PlayEntityEquipment, 0x47, Play, ClientBound => PlayEntityEquiptmentSpec {
+ entity_id: VarInt,
+ slot: EquipmentSlot,
+ item: Option<Slot>
+ },
+ PlaySetExperience, 0x48, Play, ClientBound => PlaySetExperienceSpec {
+ experience_bar: f32,
+ level: VarInt,
+ total_experience: VarInt
+ },
+ PlayUpdatehealth, 0x49, Play, ClientBound => PlayUpdateHealthSpec {
+ health: f32,
+ food: VarInt,
+ saturation: f32
+ },
+ PlayScoreboardObjective, 0x4A, Play, ClientBound => PlayScoreboardObjectiveSpec {
+ objective_name: String,
+ action: ScoreboardObjectiveAction
+ },
+ PlaySetPassengers, 0x4B, Play, ClientBound => PlaySetPassengersSpec {
+ entity_id: VarInt,
+ passenger_entitiy_ids: VarIntCountedArray<VarInt>
+ },
+ // todo teams
+ // todo update score
+ PlaySpawnPosition, 0x4E, Play, ClientBound => PlaySpawnPositionSpec {
+ location: IntPosition
+ },
+ PlayTimeUpdate, 0x4F, Play, ClientBound => PlayTimeUpdateSpec {
+ world_age: i64,
+ time_of_day: i64
+ },
+ // todo title
+ PlayEntitySoundEffect, 0x51, Play, ClientBound => PlayEntitySoundEffectSpec {
+ sound_id: VarInt,
+ sound_category: SoundCategory,
+ entity_id: VarInt,
+ volume: f32,
+ pitch: f32
+ },
+ PlaySoundEffect, 0x52, Play, ClientBound => PlaySoundEffectSpec {
+ sound_id: VarInt,
+ sound_category: SoundCategory,
+ position_x: FixedInt,
+ position_y: FixedInt,
+ position_z: FixedInt,
+ volume: f32,
+ pitch: f32
+ },
+ // todo stop sound
+ PlayerPlayerListHeaderAndFooter, 0x54, Play, ClientBound => PlayPlayerListHeaderAndFooterSpec {
+ header: Chat,
+ footer: Chat
+ },
+ PlayNbtQueryResponse, 0x55, Play, ClientBound => PlayNbtQueryResponseSpec {
+ transaction_id: VarInt,
+ nbt: NamedNbtTag
+ },
+ PlayCollectItem, 0x56, Play, ClientBound => PlayCollectItemSpec {
+ collected_entity_id: VarInt,
+ collector_entity_id: VarInt,
+ pickup_item_count: VarInt
+ },
+ PlayEntityTeleport, 0x57, Play, ClientBound => PlayEntityTeleportSpec {
+ entity_id: VarInt,
+ x: f64,
+ y: f64,
+ z: f64,
+ yaw: Angle,
+ pitch: Angle,
+ on_ground: bool
+ },
+ // todo advancements
+ PlayAdvancements, 0x58, Play, ClientBound => PlayAdvancementsSpec {
+ raw: RemainingBytes
+ },
+ PlayEntityProperties, 0x59, Play, ClientBound => PlayEntityPropertiesSpec {
+ entity_id: VarInt,
+ properties: IntCountedArray<EntityPropertySpec>
+ },
+ PlayEntityEffect, 0x5A, Play, ClientBound => PlayEntityEffectSpec {
+ entity_id: VarInt,
+ effect_id: EntityEffectKind,
+ amplifier: i8,
+ duration_ticks: VarInt,
+ flags: EntityEffectFlags
+ },
+ PlayDeclareRecipes, 0x5B, Play, ClientBound => PlayDeclareRecipesSpec {
+ recipes: VarIntCountedArray<RecipeSpec>
+ },
+ PlayTags, 0x5C, Play, ClientBound => PlayTagsSpec {
+ block_tags: VarIntCountedArray<TagSpec>,
+ item_tags: VarIntCountedArray<TagSpec>,
+ fluid_tags: VarIntCountedArray<TagSpec>,
+ entity_tags: VarIntCountedArray<TagSpec>
+ },
+
+ // play server bound
+ PlayTeleportConfirm, 0x00, Play, ServerBound => PlayTeleportConfirmSpec {
+ teleport_id: VarInt
+ },
+ PlayQueryBlockNbt, 0x01, Play, ServerBound => PlayQueryBlockNbtSpec {
+ transaction_id: VarInt,
+ location: IntPosition
+ },
+ PlayQueryEntityNbt, 0x0D, Play, ServerBound => PlayQueryEntityNbtSpec {
+ transaction_id: VarInt,
+ entity_id: VarInt
+ },
+ PlaySetDifficulty, 0x02, Play, ServerBound => PlaySetDifficultySpec {
+ new_difficulty: Difficulty
+ },
+ PlayClientChatMessage, 0x03, Play, ServerBound => PlayClientChatMessageSpec {
+ message: String
+ },
+ PlayClientStatus, 0x04, Play, ServerBound => PlayClientStatusSpec {
+ action: ClientStatusAction
+ },
+ PlayClientSettings, 0x05, Play, ServerBound => PlayClientSettingsSpec {
+ locale: String,
+ view_distance: i8,
+ chat_mode: ClientChatMode,
+ chat_colors: bool,
+ displayed_skin_parts: ClientDisplayedSkinParts,
+ main_hand: ClientMainHand
+ },
+ PlayClientTabComplete, 0x06, Play, ServerBound => PlayClientTabCompleteSpec {
+ transaction_id: VarInt,
+ text: String
+ },
+ PlayClientWindowConfirmation, 0x07, Play, ServerBound => PlayClientWindowConfirmationSpec {
+ window_id: i8,
+ action_num: i16,
+ accepted: bool
+ },
+ PlayClickWindowButton, 0x08, Play, ServerBound => PlayClickWindowButtonSpec {
+ window_id: i8,
+ button_id: i8
+ },
+ PlayClickWindow, 0x09, Play, ServerBound => PlayClickWindowSpec {
+ window_id: u8,
+ slot: i16,
+ button: i8,
+ action_number: i16,
+ mode: InventoryOperationMode,
+ clicked_item: Option<Slot>
+ },
+ PlayClientCloseWindow, 0x0A, Play, ServerBound => PlayClientCloseWindowSpec {
+ window_id: u8
+ },
+ PlayClientPluginMessage, 0x0B, Play, ServerBound => PlayClientPluginMessageSpec {
+ channel: String,
+ data: RemainingBytes
+ },
+ PlayEditBook, 0x0C, Play, ServerBound => PlayEditBookSpec {
+ new_book: Option<Slot>,
+ is_signing: bool,
+ hand: Hand
+ },
+ PlayInteractEntity, 0x0E, Play, ServerBound => PlayInteractEntitySpec {
+ entity_id: VarInt,
+ kind: InteractKind
+ },
+ PlayClientKeepAlive, 0x0F, Play, ServerBound => PlayClientKeepAliveSpec {
+ id: i64
+ },
+ PlayLockDifficulty, 0x10, Play, ServerBound => PlayLockDifficultySpec {
+ locked: bool
+ },
+ PlayPlayerPosition, 0x11, Play, ServerBound => PlayPlayerPositionSpec {
+ x: f64,
+ feet_y: f64,
+ z: f64,
+ on_ground: bool
+ },
+ PlayClientPlayerPositionAndRotation, 0x12, Play, ServerBound => PlayClientPlayerPositionAndRotationSpec {
+ x: f64,
+ feet_y: f64,
+ z: f64,
+ yaw: f32,
+ pitch: f32,
+ on_ground: bool
+ },
+ PlayPlayerRotation, 0x13, Play, ServerBound => PlayPlayerRotationSpec {
+ yaw: f32,
+ pitch: f32,
+ on_ground: bool
+ },
+ PlayPlayerMovement, 0x14, Play, ServerBound => PlayPlayerMovementSpec {
+ on_ground: bool
+ },
+ PlayClientVehicleMove, 0x15, Play, ServerBound => PlayClientVehicleMoveSpec {
+ x: f64,
+ y: f64,
+ z: f64,
+ yaw: f32,
+ pitch: f32
+ },
+ PlaySteerBoat, 0x16, Play, ServerBound => PlaySteerBoatSpec {
+ left_paddle_turning: bool,
+ right_paddle_turning: bool
+ },
+ PlayPickItem, 0x17, Play, ServerBound => PlayPickItemSpec {
+ slot_idx: VarInt
+ },
+ PlayCraftRecipeRequest, 0x18, Play, ServerBound => PlayCraftRecipeRequestSpec {
+ window_id: i8,
+ recipe: String,
+ make_all: bool
+ },
+ PlayClientPlayerAbilities, 0x19, Play, ServerBound => PlayClientPlayerAbilitiesSpec {
+ flags: ClientPlayerAbilities,
+ flying_speed: f32,
+ walking_speed: f32
+ },
+ PlayPlayerDigging, 0x1A, Play, ServerBound => PlayPlayerDiggingSpec {
+ status: PlayerDiggingStatus,
+ location: IntPosition,
+ face: DiggingFace
+ },
+ PlayEntityAction, 0x1B, Play, ServerBound => PlayEntityActionSpec {
+ entity_id: VarInt,
+ action: EntityActionKind,
+ jump_boot: VarInt
+ },
+ PlaySteerVehicle, 0x1C, Play, ServerBound => PlaySteerVehicleSpec {
+ sideways: f32,
+ forward: f32,
+ flags: SteerVehicleFlags
+ },
+ // todo recipe book data
+ PlayNameItem, 0x1E, Play, ServerBound => PlayNameItemSpec {
+ name: String
+ },
+ PlayResourcePackStatus, 0x1F, Play, ServerBound => PlayResourcePackStatusSpec {
+ status: ResourcePackStatus
+ },
+ // todo advancement tab
+ PlaySelectTrade, 0x21, Play, ServerBound => PlaySelectTradeSpec {
+ selected_slot: VarInt
+ },
+ PlaySetBeaconEffect, 0x22, Play, ServerBound => PlaySetBeaconEffectSpec {
+ primary_effect: VarInt,
+ secondary_effect: VarInt
+ },
+ PlayClientHeldItemChange, 0x23, Play, ServerBound => PlayClientHeldItemChangeSpec {
+ slot: i16
+ },
+ PlayUpdateCommandBlock, 0x24, Play, ServerBound => PlayUpdateCommandBlockSpec {
+ location: IntPosition,
+ command: String,
+ mode: CommandBlockMode,
+ flags: CommandBlockFlags
+ },
+ PlayUpdateCommandBlockMinecart, 0x25, Play, ServerBound => PlayUpdateCommandBlockMinecartSpec {
+ entity_id: VarInt,
+ command: String,
+ track_output: bool
+ },
+ PlayCreativeInventoryAction, 0x26, Play, ServerBound => PlayCreativeInventoryActionSpec {
+ slot: i16,
+ clicked_item: Option<Slot>
+ },
+ PlayUpdateJigsawBlock, 0x27, Play, ServerBound => PlayUpdateJigsawBlockSpec {
+ location: IntPosition,
+ attachment_type: String,
+ target_pool: String,
+ final_state: String
+ },
+ PlayUpdateStructureBlock, 0x28, Play, ServerBound => PlayUpdateStructureBlockSpec {
+ location: IntPosition,
+ action: UpdateStructureBlockAction,
+ mode: UpdateStructureBlockMode,
+ name: String,
+ offset_x: i8,
+ offset_y: i8,
+ offset_z: i8,
+ size_x: i8,
+ size_y: i8,
+ size_z: i8,
+ mirror: UpdateStructureBlockMirror,
+ rotation: UpdateStructureBlockRotation,
+ metadata: String,
+ integrity: f32,
+ seed: VarLong,
+ flags: UpdateStructureBlockFlags
+ },
+ PlayUpdateSign, 0x29, Play, ServerBound => PlayUpdateSignSpec {
+ location: IntPosition,
+ line1: String,
+ line2: String,
+ line3: String,
+ line4: String
+ },
+ PlayClientAnimation, 0x2A, Play, ServerBound => PlayClientAnimationSpec {
+ hand: Hand
+ },
+ PlaySpectate, 0x2B, Play, ServerBound => PlaySpectateSpec {
+ target: UUID4
+ },
+ PlayBlockPlacement, 0x2C, Play, ServerBound => PlayBlockPlacementSpec {
+ hand: Hand,
+ location: IntPosition,
+ face: DiggingFace,
+ cursor_position_x: f32,
+ cursor_position_y: f32,
+ cursor_position_z: f32,
+ inside_block: bool
+ },
+ PlayUseItem, 0x2D, Play, ServerBound => PlayUseItemSpec {
+ hand: Hand
+ }
+});
+
+// helper types
+
+// handshake enum
+proto_byte_enum!(HandshakeNextState,
+ 0x01 :: Status,
+ 0x02 :: Login
+);
+
+macro_rules! counted_array_type {
+ ($name: ident, $countert: ty, $tousize_fn: ident, $fromusize_fn: ident) => {
+ #[derive(Debug, Clone, PartialEq)]
+ pub struct $name<T> where T: Debug + Clone + PartialEq {
+ pub data: Vec<T>
+ }
+
+ impl<T> Serialize for $name<T> where T: Serialize + Debug + Clone + PartialEq {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let count: $countert = $fromusize_fn(self.data.len());
+ to.serialize_other(&count)?;
+
+ for entry in &self.data {
+ to.serialize_other(entry)?;
+ }
+
+ Ok(())
+ }
+ }
+
+ impl<T> Deserialize for $name<T> where T: Deserialize + Debug + Clone + PartialEq {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized{value: raw_count, mut data} = <$countert>::mc_deserialize(data)?;
+ let count: usize = $tousize_fn(raw_count);
+
+ let mut out = Vec::with_capacity(count);
+ for _ in 0..count {
+ let Deserialized{value: next, data: rest} = T::mc_deserialize(data)?;
+ data = rest;
+ out.push(next);
+ }
+
+ Deserialized::ok(Self { data: out }, data)
+ }
+ }
+
+ impl<T> Into<Vec<T>> for $name<T> where T: Debug + Clone + PartialEq {
+ fn into(self) -> Vec<T> {
+ self.data
+ }
+ }
+
+ impl<T> From<Vec<T>> for $name<T> where T: Debug + Clone + PartialEq {
+ fn from(data: Vec<T>) -> Self {
+ Self { data }
+ }
+ }
+ }
+}
+
+#[inline]
+fn varint_to_usize(v: VarInt) -> usize {
+ v.into()
+}
+
+#[inline]
+fn varint_from_usize(u: usize) -> VarInt {
+ u.into()
+}
+counted_array_type!(VarIntCountedArray, VarInt, varint_to_usize, varint_from_usize);
+
+#[inline]
+fn i16_to_usize(v: i16) -> usize {
+ v as usize
+}
+
+#[inline]
+fn i16_from_usize(u: usize) -> i16 {
+ u as i16
+}
+counted_array_type!(ShortCountedArray, i16, i16_to_usize, i16_from_usize);
+
+#[inline]
+fn i32_to_usize(v: i32) -> usize {
+ v as usize
+}
+
+#[inline]
+fn i32_from_usize(u: usize) -> i32 {
+ u as i32
+}
+counted_array_type!(IntCountedArray, i32, i32_to_usize, i32_from_usize);
+
+#[inline]
+fn i8_to_usize(v: i8) -> usize {
+ v as usize
+}
+
+#[inline]
+fn i8_from_usize(u: usize) -> i8 {
+ u as i8
+}
+counted_array_type!(ByteCountedArray, i8, i8_to_usize, i8_from_usize);
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct RemainingBytes {
+ pub data: Vec<u8>,
+}
+
+impl Serialize for RemainingBytes {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_bytes(self.data.as_slice())
+ }
+}
+
+impl Deserialize for RemainingBytes {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Deserialized::ok(RemainingBytes { data: Vec::from(data) }, &[])
+ }
+}
+
+impl Into<Vec<u8>> for RemainingBytes {
+ fn into(self) -> Vec<u8> {
+ self.data
+ }
+}
+
+impl From<Vec<u8>> for RemainingBytes {
+ fn from(data: Vec<u8>) -> Self {
+ Self { data }
+ }
+}
+
+proto_byte_enum!(CardinalDirection,
+ 0x00 :: South,
+ 0x01 :: West,
+ 0x02 :: North,
+ 0x03:: East
+);
+
+proto_byte_enum!(EntityAnimationKind,
+ 0x00 :: SwingMainArm,
+ 0x01 :: TakeDamage,
+ 0x02 :: LeaveBed,
+ 0x03 :: SwingOffHand,
+ 0x04 :: CriticalEffect,
+ 0x05 :: MagicCriticalEffect
+);
+
+proto_varint_enum!(StatisticCategory,
+ 0x00 :: Mined,
+ 0x01 :: Crafted,
+ 0x02 :: Used,
+ 0x03 :: Broken,
+ 0x04 :: PickedUp,
+ 0x05 :: Dropped,
+ 0x06 :: Killed,
+ 0x07 :: KilledBy,
+ 0x08 :: Custom
+);
+
+proto_varint_enum!(StatisticKind,
+ 0x00 :: LeaveGame,
+ 0x01 :: PlayOneMinute,
+ 0x02 :: TimeSinceDeath,
+ 0x03 :: SneakTime,
+ 0x04 :: WealkOneCm,
+ 0x05 :: CrouchOneCm,
+ 0x06 :: SprintOneCm,
+ 0x07 :: SwimOneCm,
+ 0x08 :: FallOneCm,
+ 0x09 :: ClimbOneCm,
+ 0x0A :: FlyOneCm,
+ 0x0B :: DiveOneCm,
+ 0x0C :: MinecartOneCm,
+ 0x0D :: BoatOneCm,
+ 0x0E :: PigOneCm,
+ 0x0F :: HorseOneCm,
+ 0x10 :: AviateOneCm,
+ 0x11 :: Jumps,
+ 0x12 :: Drops,
+ 0x13 :: DamageDealt,
+ 0x14 :: DamageTaken,
+ 0x15 :: Deaths,
+ 0x16 :: MobKills,
+ 0x17 :: AnimalsBread,
+ 0x18 :: PlayerKills,
+ 0x19 :: FishCaught,
+ 0x1A :: TalkedToVillager,
+ 0x1B :: TradedWithVillager,
+ 0x1C :: EatCakeSlice,
+ 0x1D :: FillCauldron,
+ 0x1E :: UseCauldron,
+ 0x1F :: CleanArmor,
+ 0x20 :: CleanBanner,
+ 0x21 :: InteractWithBrewingStand,
+ 0x22 :: InteractWithBeaccon,
+ 0x23 :: InspectDropper,
+ 0x24 :: InspectHopper,
+ 0x25 :: InspectDispenser,
+ 0x26 :: PlayNoteBlock,
+ 0x27 :: TuneNoteBlock,
+ 0x28 :: PotFlower,
+ 0x29 :: TriggerTrappedChest,
+ 0x2A :: OpenEnderChest,
+ 0x2B :: EnchantItem,
+ 0x2C :: PlayRecord,
+ 0x2D :: InteractWithFurnace,
+ 0x2E :: InteractWithCraftingTable,
+ 0x2F :: OpenChest,
+ 0x30 :: SleepInBed,
+ 0x31 :: OpenShulkerBox
+);
+
+__protocol_body_def_helper!(Statistic {
+ category: StatisticCategory,
+ statistic: StatisticKind,
+ value: VarInt
+});
+
+proto_byte_enum!(DiggingStatus,
+ 0x00 :: Started,
+ 0x01 :: Cancelled,
+ 0x02 :: Finished
+);
+
+proto_byte_enum!(BlockEntityDataAction,
+ 0x01 :: SetMobSpawnerData,
+ 0x02 :: SetCommandBlockText,
+ 0x03 :: SetBeaconLevelAndPower,
+ 0x04 :: SetMobHeadRotationAndSkin,
+ 0x05 :: DeclareConduit,
+ 0x06 :: SetBannerColorAndPatterns,
+ 0x07 :: SetStructureTileEntityData,
+ 0x08 :: SetEndGatewayDestination,
+ 0x09 :: SetSignText,
+ 0x0B :: DeclareBed,
+ 0x0C :: SetJigsawBlockData,
+ 0x0D :: SetCampfireItems,
+ 0x0E :: BeehiveInformation
+);
+
+proto_byte_enum!(Difficulty,
+ 0x00 :: Peaceful,
+ 0x01 :: Easy,
+ 0x02 :: Normal,
+ 0x03 :: Hard
+);
+
+proto_byte_enum!(ChatPosition,
+ 0x00 :: ChatBox,
+ 0x01 :: SystemMessage,
+ 0x02 :: Hotbar
+);
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub struct BlockChangeHorizontalPosition {
+ pub rel_x: u8,
+ pub rel_y: u8,
+}
+
+impl Serialize for BlockChangeHorizontalPosition {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte((self.rel_x & 0xF) << 4 | (self.rel_y & 0xF))
+ }
+}
+
+impl Deserialize for BlockChangeHorizontalPosition {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ Ok(u8::mc_deserialize(data)?.map(move |b| {
+ BlockChangeHorizontalPosition {
+ rel_x: (b >> 4) & 0xF,
+ rel_y: b & 0xF,
+ }
+ }))
+ }
+}
+
+__protocol_body_def_helper!(MultiBlockChangeRecord {
+ horizontal_position: BlockChangeHorizontalPosition,
+ y_coordinate: u8,
+ block_id: VarInt
+});
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum BossBarAction {
+ Add(BossBarAddSpec),
+ Remove,
+ UpdateHealth(BossBarUpdateHealthSpec),
+ UpdateTitle(BossBarUpdateTitleSpec),
+ UpdateStyle(BossBarUpdateStyleSpec),
+ UpdateFlags(BossBarUpdateFlagsSpec),
+}
+
+impl Serialize for BossBarAction {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ use BossBarAction::*;
+ to.serialize_other(&VarInt(match self {
+ Add(_) => 0x00,
+ Remove => 0x01,
+ UpdateHealth(_) => 0x02,
+ UpdateTitle(_) => 0x03,
+ UpdateStyle(_) => 0x04,
+ UpdateFlags(_) => 0x05,
+ }))?;
+ match self {
+ Add(body) => to.serialize_other(body),
+ Remove => Ok(()),
+ UpdateHealth(body) => to.serialize_other(body),
+ UpdateTitle(body) => to.serialize_other(body),
+ UpdateStyle(body) => to.serialize_other(body),
+ UpdateFlags(body) => to.serialize_other(body),
+ }
+ }
+}
+
+impl Deserialize for BossBarAction {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: type_id, data } = VarInt::mc_deserialize(data)?;
+ use BossBarAction::*;
+ match type_id.0 {
+ 0x00 => Ok(BossBarAddSpec::mc_deserialize(data)?.map(move |body| Add(body))),
+ 0x01 => Deserialized::ok(Remove, data),
+ 0x02 => Ok(BossBarUpdateHealthSpec::mc_deserialize(data)?.map(move |body| UpdateHealth(body))),
+ 0x03 => Ok(BossBarUpdateTitleSpec::mc_deserialize(data)?.map(move |body| UpdateTitle(body))),
+ 0x04 => Ok(BossBarUpdateStyleSpec::mc_deserialize(data)?.map(move |body| UpdateStyle(body))),
+ 0x05 => Ok(BossBarUpdateFlagsSpec::mc_deserialize(data)?.map(move |body| UpdateFlags(body))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid boss bar action id {:x}", other)))
+ }
+ }
+}
+
+proto_varint_enum!(BossBarColor,
+ 0x00 :: Pink,
+ 0x01 :: Blue,
+ 0x02 :: Red,
+ 0x03 :: Green,
+ 0x04 :: Yellow,
+ 0x05 :: Purple,
+ 0x06 :: White
+);
+
+proto_varint_enum!(BossBarDivision,
+ 0x00 :: NoDivision,
+ 0x01 :: SixNotches,
+ 0x02 :: TenNotches,
+ 0x03 :: TwelveNotches,
+ 0x04 :: TwentyNotches
+);
+
+proto_byte_flag!(BossBarFlags,
+ 0x01 :: darken_sky,
+ 0x02 :: dragon_bar,
+ 0x04 :: create_fog
+);
+
+__protocol_body_def_helper!(BossBarAddSpec {
+ title: Chat,
+ health: f32,
+ color: BossBarColor,
+ division: BossBarDivision,
+ flags: BossBarFlags
+});
+
+__protocol_body_def_helper!(BossBarUpdateHealthSpec {
+ health: f32
+});
+
+__protocol_body_def_helper!(BossBarUpdateTitleSpec {
+ title: String
+});
+
+__protocol_body_def_helper!(BossBarUpdateStyleSpec {
+ color: BossBarColor,
+ dividers: BossBarDivision
+});
+
+__protocol_body_def_helper!(BossBarUpdateFlagsSpec {
+ flags: BossBarFlags
+});
+
+__protocol_body_def_helper!(TabCompleteMatch {
+ match_: String,
+ tooltip: Option<Chat>
+});
+
+proto_varint_enum!(SoundCategory,
+ 0x00 :: Master,
+ 0x01 :: Music,
+ 0x02 :: Records,
+ 0x03 :: Weather,
+ 0x04 :: Block,
+ 0x05 :: Hostile,
+ 0x06 :: Neutral,
+ 0x07 :: Player,
+ 0x08 :: Ambient,
+ 0x09 :: Voice
+);
+
+__protocol_body_def_helper!(ExposionRecord {
+ x: i8,
+ y: i8,
+ z: i8
+});
+
+proto_byte_enum!(GameMode,
+ 0x00 :: Survival,
+ 0x01 :: Creative,
+ 0x02 :: Adventure,
+ 0x03 :: Spectator
+);
+
+proto_byte_enum!(WinGameAction,
+ 0x00 :: Respawn,
+ 0x01 :: RollCreditsAndRespawn
+);
+
+proto_byte_enum!(DemoEvent,
+ 0x00 :: ShowWelcomeScreen,
+ 0x65 :: TellMovementControls,
+ 0x66 :: TellJumpControl,
+ 0x67 :: TellInventoryControl,
+ 0x68 :: EndDemo
+);
+
+proto_byte_enum!(RespawnRequestType,
+ 0x00 :: Screen,
+ 0x01 :: Immediate
+);
+
+proto_int_enum!(Dimension,
+ -0x01 :: Nether,
+ 0x00 :: Overworld,
+ 0x01 :: End
+);
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum GameChangeReason {
+ NoRespawnAvailable,
+ EndRaining,
+ BeginRaining,
+ ChangeGameMode(GameMode),
+ WinGame(WinGameAction),
+ Demo(DemoEvent),
+ ArrowHitPlayer,
+ RainLevelChange(f32),
+ ThunderLevelChange(f32),
+ PufferfishSting,
+ ElderGuardianMobAppearance,
+ Respawn(RespawnRequestType),
+}
+
+impl Serialize for GameChangeReason {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ use GameChangeReason::*;
+ to.serialize_byte(match self {
+ NoRespawnAvailable => 0x00,
+ EndRaining => 0x01,
+ BeginRaining => 0x02,
+ ChangeGameMode(_) => 0x03,
+ WinGame(_) => 0x04,
+ Demo(_) => 0x05,
+ ArrowHitPlayer => 0x06,
+ RainLevelChange(_) => 0x07,
+ ThunderLevelChange(_) => 0x08,
+ PufferfishSting => 0x09,
+ ElderGuardianMobAppearance => 0x0A,
+ Respawn(_) => 0x0B
+ })?;
+
+ let value = match self {
+ ChangeGameMode(body) => {
+ body.as_byte() as f32
+ }
+ WinGame(body) => {
+ body.as_byte() as f32
+ }
+ Demo(body) => {
+ body.as_byte() as f32
+ }
+ RainLevelChange(body) => *body,
+ ThunderLevelChange(body) => *body,
+ Respawn(body) => {
+ body.as_byte() as f32
+ }
+ _ => 0 as f32
+ };
+ to.serialize_other(&value)
+ }
+}
+
+impl Deserialize for GameChangeReason {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: reason_id, data } = u8::mc_deserialize(data)?;
+ let Deserialized { value, data } = f32::mc_deserialize(data)?;
+ use GameChangeReason::*;
+ let out = match reason_id {
+ 0x00 => Ok(NoRespawnAvailable),
+ 0x01 => Ok(EndRaining),
+ 0x02 => Ok(BeginRaining),
+ 0x03 => Ok(ChangeGameMode(
+ GameMode::from_byte(value as u8)
+ .map(move |v| Ok(v))
+ .unwrap_or_else(|| Err(DeserializeErr::CannotUnderstandValue(format!("unknown gamemode value {}", value))))?)),
+ 0x04 => Ok(WinGame(
+ WinGameAction::from_byte(value as u8)
+ .map(move |v| Ok(v))
+ .unwrap_or_else(|| Err(DeserializeErr::CannotUnderstandValue(format!("unknown WinGame value {}", value))))?)),
+ 0x05 => Ok(Demo(
+ DemoEvent::from_byte(value as u8)
+ .map(move |v| Ok(v))
+ .unwrap_or_else(|| Err(DeserializeErr::CannotUnderstandValue(format!("unknown DemoEvent value {}", value))))?)),
+ 0x06 => Ok(ArrowHitPlayer),
+ 0x07 => Ok(RainLevelChange(value)),
+ 0x08 => Ok(ThunderLevelChange(value)),
+ 0x09 => Ok(PufferfishSting),
+ 0x0A => Ok(ElderGuardianMobAppearance),
+ 0x0B => Ok(Respawn(
+ RespawnRequestType::from_byte(value as u8)
+ .map(move |v| Ok(v))
+ .unwrap_or_else(|| Err(DeserializeErr::CannotUnderstandValue(format!("invalid respawn reason {}", value))))?)),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid game change reason id {}", other)))
+ }?;
+
+ Deserialized::ok(out, data)
+ }
+}
+
+proto_varint_enum!(MapIconType,
+ 0x00 :: WhiteArrow,
+ 0x01 :: GreenArrow,
+ 0x02 :: RedArrow,
+ 0x03 :: BlueArrow,
+ 0x04 :: WhiteCross,
+ 0x05 :: RedPointer,
+ 0x06 :: WhiteCircle,
+ 0x07 :: SmallWhiteCircle,
+ 0x08 :: Mansion,
+ 0x09 :: Temple,
+ 0x0A :: WhiteBanner,
+ 0x0B :: OrangeBanner,
+ 0x0C :: MagentaBanner,
+ 0x0D :: YellowBanner,
+ 0x0E :: LimeBanner,
+ 0x0F :: PinkBanner,
+ 0x10 :: GrayBanner,
+ 0x11 :: LightGrayBanner,
+ 0x12 :: CyanBanner,
+ 0x13 :: PurpleBanner,
+ 0x14 :: BlueBanner,
+ 0x15 :: BrownBanner,
+ 0x16 :: GreenBanner,
+ 0x17 :: RedBanner,
+ 0x18 :: BlackBanner,
+ 0x19 :: TreasureMarker
+);
+
+__protocol_body_def_helper!(MapIconSpec {
+ kind: MapIconType,
+ x: i8,
+ z: i8,
+ direction: i8,
+ display_name: Option<Chat>
+});
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum MapColumns {
+ NoUpdates,
+ Updated(MapColumnsSpec),
+}
+
+__protocol_body_def_helper!(MapColumnsSpec {
+ columns: u8,
+ rows: u8,
+ x: u8,
+ z: u8,
+ data: VarIntCountedArray<u8>
+});
+
+impl Serialize for MapColumns {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ use MapColumns::*;
+ match self {
+ NoUpdates => to.serialize_other(&0u8),
+ Updated(body) => to.serialize_other(body),
+ }
+ }
+}
+
+impl Deserialize for MapColumns {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: columns, data: rest } = u8::mc_deserialize(data)?;
+ use MapColumns::*;
+ match columns {
+ 0x00 => Deserialized::ok(NoUpdates, rest),
+ _ => Ok(MapColumnsSpec::mc_deserialize(data)?.map(move |v| Updated(v)))
+ }
+ }
+}
+
+impl Into<Option<MapColumnsSpec>> for MapColumns {
+ fn into(self) -> Option<MapColumnsSpec> {
+ use MapColumns::*;
+ match self {
+ NoUpdates => None,
+ Updated(body) => Some(body)
+ }
+ }
+}
+
+impl From<Option<MapColumnsSpec>> for MapColumns {
+ fn from(other: Option<MapColumnsSpec>) -> Self {
+ use MapColumns::*;
+ match other {
+ Some(body) => Updated(body),
+ None => NoUpdates
+ }
+ }
+}
+
+__protocol_body_def_helper!(TradeSpec {
+ input_item_1: Option<Slot>,
+ output_item: Option<Slot>,
+ input_item_2: Option<Slot>,
+ trade_disabled: bool,
+ trade_uses: i32,
+ max_trade_uses: i32,
+ xp: i32,
+ special_price: i32,
+ price_multiplier: f32,
+ demand: i32
+});
+
+proto_varint_enum!(Hand,
+ 0x00 :: MainHand,
+ 0x01 :: OffHand
+);
+
+proto_varint_enum!(WindowType,
+ 0x00 :: GenericOneRow,
+ 0x01 :: GenericTwoRow,
+ 0x02 :: GenericThreeRow,
+ 0x03 :: GenericFourRow,
+ 0x04 :: GenericFiveRow,
+ 0x05 :: GenericSixRow,
+ 0x06 :: GenericSquare,
+ 0x07 :: Anvil,
+ 0x08 :: Beacon,
+ 0x09 :: BlastFurnace,
+ 0x0A :: BrewingStand,
+ 0x0B :: CraftingTable,
+ 0x0C :: EnchantmentTable,
+ 0x0D :: Furnace,
+ 0x0E :: Grindstone,
+ 0x0F :: Hopper,
+ 0x10 :: Lectern,
+ 0x11 :: Loom,
+ 0x12 :: Merchant,
+ 0x13 :: ShulkerBox,
+ 0x14 :: Smoker,
+ 0x15 :: Cartography,
+ 0x16 :: StoneCutter
+);
+
+proto_byte_flag!(PlayerAbilityFlags,
+ 0x01 :: invulnerable,
+ 0x02 :: flying,
+ 0x04 :: allow_flying,
+ 0x08 :: instant_break
+);
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum CombatEvent {
+ Enter,
+ End(CombatEndSpec),
+ EntityDead(CombatEntityDeadSpec),
+}
+
+__protocol_body_def_helper!(CombatEndSpec {
+ duration_ticks: VarInt,
+ entity_id: i32
+});
+
+__protocol_body_def_helper!(CombatEntityDeadSpec {
+ player_id: VarInt,
+ entity_id: i32,
+ message: Chat
+});
+
+impl Serialize for CombatEvent {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ use CombatEvent::*;
+ to.serialize_other(&VarInt(match self {
+ Enter => 0x00,
+ End(_) => 0x01,
+ EntityDead(_) => 0x02
+ }))?;
+
+ match self {
+ End(body) => to.serialize_other(body)?,
+ EntityDead(body) => to.serialize_other(body)?,
+ _ => {}
+ }
+
+ Ok(())
+ }
+}
+
+impl Deserialize for CombatEvent {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: action_id, data } = VarInt::mc_deserialize(data)?;
+
+ use CombatEvent::*;
+ match action_id.0 {
+ 0x00 => Deserialized::ok(Enter, data),
+ 0x01 => Ok(CombatEndSpec::mc_deserialize(data)?.map(move |body| End(body))),
+ 0x02 => Ok(CombatEntityDeadSpec::mc_deserialize(data)?.map(move |body| EntityDead(body))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid combat event id {:?}", other)))
+ }
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct PlayerInfoAction<A: Clone + PartialEq + Debug> {
+ pub uuid: UUID4,
+ pub action: A
+}
+
+impl<A> Serialize for PlayerInfoAction<A> where A: Serialize + Clone + PartialEq + Debug {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&self.uuid)?;
+ to.serialize_other(&self.action)
+ }
+}
+
+impl<A> Deserialize for PlayerInfoAction<A> where A: Deserialize + Clone + PartialEq + Debug {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: uuid, data } = UUID4::mc_deserialize(data)?;
+ Ok(A::mc_deserialize(data)?.map(move |action| Self { uuid, action }))
+ }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum PlayerInfoActionList {
+ Add(Vec<PlayerInfoAction<PlayerAddActionSpec>>),
+ UpdateGameMode(Vec<PlayerInfoAction<GameMode>>),
+ UpdateLatency(Vec<PlayerInfoAction<VarInt>>),
+ UpdateDisplayName(Vec<PlayerInfoAction<Option<Chat>>>),
+ Remove(Vec<UUID4>)
+}
+
+__protocol_body_def_helper!(PlayerAddActionSpec {
+ name: String,
+ properties: VarIntCountedArray<PlayerAddProperty>,
+ game_mode: GameMode,
+ ping_ms: VarInt,
+ display_name: Option<Chat>
+});
+
+__protocol_body_def_helper!(PlayerAddProperty {
+ name: String,
+ value: String,
+ signature: Option<String>
+});
+
+impl PlayerInfoActionList {
+
+ pub fn player_ids(&self) -> Vec<UUID4> {
+ use PlayerInfoActionList::*;
+
+ match self {
+ Add(vec) => vec.iter().map(move |v| v.uuid).collect(),
+ UpdateGameMode(vec) => vec.iter().map(move |v| v.uuid).collect(),
+ UpdateLatency(vec) => vec.iter().map(move |v| v.uuid).collect(),
+ UpdateDisplayName(vec) => vec.iter().map(move |v| v.uuid).collect(),
+ Remove(vec) => vec.clone()
+ }
+ }
+
+ pub fn id(&self) -> VarInt {
+ use PlayerInfoActionList::*;
+
+ match self {
+ Add(_) => 0x00,
+ UpdateGameMode(_) => 0x01,
+ UpdateLatency(_) => 0x02,
+ UpdateDisplayName(_) => 0x03,
+ Remove(_) => 0x04
+ }.into()
+ }
+
+ pub fn len(&self) -> usize {
+ use PlayerInfoActionList::*;
+
+ match self {
+ Add(vec) => vec.len(),
+ UpdateGameMode(vec) => vec.len(),
+ UpdateLatency(vec) => vec.len(),
+ UpdateDisplayName(vec) => vec.len(),
+ Remove(vec) => vec.len()
+ }
+ }
+}
+
+impl Serialize for PlayerInfoActionList {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let id = self.id();
+ to.serialize_other(&id)?;
+
+ let len = VarInt(self.len() as i32);
+ to.serialize_other(&len)?;
+
+ use PlayerInfoActionList::*;
+
+ match self {
+ Add(body) => serialize_vec_directly(body, to),
+ UpdateGameMode(body) => serialize_vec_directly(body, to),
+ UpdateLatency(body) => serialize_vec_directly(body, to),
+ UpdateDisplayName(body) => serialize_vec_directly(body, to),
+ Remove(body) => serialize_vec_directly(body, to),
+ }
+ }
+}
+
+impl Deserialize for PlayerInfoActionList {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: action_id, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: raw_count, mut data } = VarInt::mc_deserialize(data)?;
+
+ let count = raw_count.0 as usize;
+
+ use PlayerInfoActionList::*;
+ match action_id.0 {
+ 0x00 => Ok(deserialize_vec_directly(count, &mut data)?.map(move |v| Add(v))),
+ 0x01 => Ok(deserialize_vec_directly(count, &mut data)?.map(move |v| UpdateGameMode(v))),
+ 0x02 => Ok(deserialize_vec_directly(count, &mut data)?.map(move |v| UpdateLatency(v))),
+ 0x03 => Ok(deserialize_vec_directly(count, &mut data)?.map(move |v| UpdateDisplayName(v))),
+ 0x04 => Ok(deserialize_vec_directly(count, &mut data)?.map(move |v| Remove(v))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid player info action id {}", other))),
+ }
+ }
+}
+
+fn serialize_vec_directly<I: Serialize, S: Serializer>(items: &Vec<I>, to: &mut S) -> SerializeResult {
+ for item in items {
+ to.serialize_other(item)?;
+ }
+
+ Ok(())
+}
+
+fn deserialize_vec_directly<I: Deserialize>(count: usize, mut data: &[u8]) -> DeserializeResult<Vec<I>> {
+ let mut out = Vec::with_capacity(count);
+ for _ in 0..count {
+ let Deserialized { value: item, data: rest } = I::mc_deserialize(data)?;
+ data = rest;
+ out.push(item);
+ }
+
+ Deserialized::ok(out, data)
+}
+
+proto_varint_enum!(FacePlayerKind,
+ 0x00 :: Feet,
+ 0x01 :: Eyes
+);
+
+__protocol_body_def_helper!(FacePlayerEntityTarget {
+ entity_id: VarInt,
+ kind: FacePlayerKind
+});
+
+proto_byte_flag!(PositionAndLookFlags,
+ 0x01 :: x,
+ 0x02 :: y,
+ 0x04 :: z,
+ 0x08 :: y_rotation,
+ 0x10 :: x_rotation
+);
+
+proto_byte_enum!(EntityEffectKind,
+ 0x01 :: Speed,
+ 0x02 :: Slowness,
+ 0x03 :: Haste,
+ 0x04 :: MiningFatigue,
+ 0x05 :: Strength,
+ 0x06 :: InstantHealth,
+ 0x07 :: InstantDamage,
+ 0x08 :: JumpBoost,
+ 0x09 :: Nausea,
+ 0x0A :: Regeneration,
+ 0x0B :: Resistance,
+ 0x0C :: FireResistance,
+ 0x0D :: WaterBreathing,
+ 0x0E :: Invisibility,
+ 0x0F :: Blindness,
+ 0x10 :: NightVision,
+ 0x11 :: Hunger,
+ 0x12 :: Weakness,
+ 0x13 :: Poison,
+ 0x14 :: Wither,
+ 0x15 :: HealthBoost,
+ 0x16 :: Absorption,
+ 0x17 :: Saturation,
+ 0x18 :: Glowing,
+ 0x19 :: Levetation,
+ 0x1A :: Luck,
+ 0x1B :: Unluck,
+ 0x1C :: SlowFalling,
+ 0x1D :: ConduitPower,
+ 0x1E :: DolphinsGrace,
+ 0x1F :: BadOmen,
+ 0x20 :: HeroOfTheVillage
+);
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum WorldBorderAction {
+ SetSize(WorldBorderSetSizeSpec),
+ LerpSize(WorldBorderLerpSizeSpec),
+ SetCenter(WorldBorderSetCenterSpec),
+ Initialize(WorldBorderInitiaializeSpec),
+ SetWarningTime(WorldBorderWarningTimeSpec),
+ SetWarningBlocks(WorldBorderWarningBlocksSpec)
+}
+
+__protocol_body_def_helper!(WorldBorderSetSizeSpec {
+ diameter: f64
+});
+
+__protocol_body_def_helper!(WorldBorderLerpSizeSpec {
+ old_diameter: f64,
+ new_diameter: f64,
+ speed: VarLong
+});
+
+__protocol_body_def_helper!(WorldBorderSetCenterSpec {
+ x: f64,
+ z: f64
+});
+
+__protocol_body_def_helper!(WorldBorderInitiaializeSpec {
+ x: f64,
+ z: f64,
+ old_diameter: f64,
+ new_diameter: f64,
+ speed: VarLong,
+ portal_teleport_boundary: VarLong,
+ warning_time: VarInt,
+ warning_blocks: VarInt
+});
+
+__protocol_body_def_helper!(WorldBorderWarningTimeSpec {
+ warning_time: VarInt
+});
+
+__protocol_body_def_helper!(WorldBorderWarningBlocksSpec {
+ warning_blocks: VarInt
+});
+
+impl WorldBorderAction {
+ pub fn id(&self) -> VarInt {
+ use WorldBorderAction::*;
+ match self {
+ SetSize(_) => 0x00,
+ LerpSize(_) => 0x01,
+ SetCenter(_) => 0x02,
+ Initialize(_) => 0x03,
+ SetWarningTime(_) => 0x04,
+ SetWarningBlocks(_) => 0x05,
+ }.into()
+ }
+}
+
+impl Serialize for WorldBorderAction {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let id = self.id();
+ to.serialize_other(&id)?;
+
+ use WorldBorderAction::*;
+ match self {
+ SetSize(body) => to.serialize_other(body),
+ LerpSize(body) => to.serialize_other(body),
+ SetCenter(body) => to.serialize_other(body),
+ Initialize(body) => to.serialize_other(body),
+ SetWarningTime(body) => to.serialize_other(body),
+ SetWarningBlocks(body) => to.serialize_other(body),
+ }
+ }
+}
+
+impl Deserialize for WorldBorderAction {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized{ value: id, data } = VarInt::mc_deserialize(data)?;
+
+ use WorldBorderAction::*;
+ match id.0 {
+ 0x00 => Ok(WorldBorderSetSizeSpec::mc_deserialize(data)?.map(move |body| SetSize(body))),
+ 0x01 => Ok(WorldBorderLerpSizeSpec::mc_deserialize(data)?.map(move |body| LerpSize(body))),
+ 0x02 => Ok(WorldBorderSetCenterSpec::mc_deserialize(data)?.map(move |body| SetCenter(body))),
+ 0x03 => Ok(WorldBorderInitiaializeSpec::mc_deserialize(data)?.map(move |body| Initialize(body))),
+ 0x04 => Ok(WorldBorderWarningTimeSpec::mc_deserialize(data)?.map(move |body| SetWarningTime(body))),
+ 0x05 => Ok(WorldBorderWarningBlocksSpec::mc_deserialize(data)?.map(move |body| SetWarningBlocks(body))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid world border action id {}", other)))
+ }
+ }
+}
+
+#[derive(Clone, Copy, PartialEq, Debug, Eq)]
+pub enum ScoreboardPosition {
+ List,
+ Sidebar,
+ BelowName,
+ TeamSpecific(i8)
+}
+
+impl ScoreboardPosition {
+ pub fn id(&self) -> i8 {
+ use ScoreboardPosition::*;
+ match self {
+ List => 0x00,
+ Sidebar => 0x01,
+ BelowName => 0x02,
+ TeamSpecific(team_id) => 0x03 + team_id
+ }
+ }
+}
+
+impl Serialize for ScoreboardPosition {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_byte(self.id() as u8)
+ }
+}
+
+impl Deserialize for ScoreboardPosition {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: id, data } = i8::mc_deserialize(data)?;
+ use ScoreboardPosition::*;
+ let res = match id {
+ 0x00 => Ok(List),
+ 0x01 => Ok(Sidebar),
+ 0x02 => Ok(BelowName),
+ other => {
+ if other >= 3 && other <= 12 {
+ Ok(TeamSpecific(other - 0x03))
+ } else {
+ Err(DeserializeErr::CannotUnderstandValue(format!("invalid scoreboard position id {}", id)))
+ }
+ }
+ }?;
+ Deserialized::ok(res, data)
+ }
+}
+
+proto_varint_enum!(EquipmentSlot,
+ 0x00 :: MainHand,
+ 0x01 :: OffHand,
+ 0x02 :: ArmorBoots,
+ 0x03 :: ArmorLeggings,
+ 0x04 :: ArmorChestplate,
+ 0x05 :: ArmorHelmet
+);
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum ScoreboardObjectiveAction {
+ Create(ScoreboardObjectiveSpec),
+ Remove,
+ UpdateText(ScoreboardObjectiveSpec)
+}
+
+proto_varint_enum!(ScoreboardObjectiveKind,
+ 0x00 :: Integer,
+ 0x01 :: Hearts
+);
+
+__protocol_body_def_helper!(ScoreboardObjectiveSpec {
+ text: Chat,
+ kind: ScoreboardObjectiveKind
+});
+
+impl ScoreboardObjectiveAction {
+ pub fn id(&self) -> i8 {
+ use ScoreboardObjectiveAction::*;
+ match self {
+ Create(_) => 0x00,
+ Remove => 0x01,
+ UpdateText(_) => 0x02
+ }
+ }
+}
+
+impl Serialize for ScoreboardObjectiveAction {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let id = self.id();
+ to.serialize_other(&id)?;
+
+ use ScoreboardObjectiveAction::*;
+ match self {
+ Create(body) => to.serialize_other(body),
+ UpdateText(body) => to.serialize_other(body),
+ _ => Ok(())
+ }
+ }
+}
+
+impl Deserialize for ScoreboardObjectiveAction {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized{ value: id, data } = i8::mc_deserialize(data)?;
+ use ScoreboardObjectiveAction::*;
+ match id {
+ 0x00 => Ok(ScoreboardObjectiveSpec::mc_deserialize(data)?.map(move |body| Create(body))),
+ 0x01 => Deserialized::ok(Remove, data),
+ 0x02 => Ok(ScoreboardObjectiveSpec::mc_deserialize(data)?.map(move |body| UpdateText(body))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid scoreboard objective action id {}", other)))
+ }
+ }
+}
+
+__protocol_body_def_helper!(EntityPropertySpec {
+ key: String,
+ value: f64,
+ modifiers: VarIntCountedArray<EntityPropertyModifierSpec>
+});
+
+__protocol_body_def_helper!(EntityPropertyModifierSpec {
+ uuid: UUID4,
+ amount: f64,
+ operation: EntityPropertyModifierOperation
+});
+
+proto_byte_enum!(EntityPropertyModifierOperation,
+ 0x00 :: AddSubtractAmount,
+ 0x01 :: AddSubtractAmountPercentOfCurrent,
+ 0x02 :: MultiplyByAmountPercent
+);
+
+proto_byte_flag!(EntityEffectFlags,
+ 0x01 :: ambient,
+ 0x02 :: show_particles,
+ 0x04 :: show_icon
+);
+
+__protocol_body_def_helper!(TagSpec {
+ name: String,
+ entries: VarIntCountedArray<VarInt>
+});
+
+proto_varint_enum!(ClientStatusAction,
+ 0x00 :: PerformRespawn,
+ 0x01 :: RequestStats
+);
+
+proto_varint_enum!(ClientChatMode,
+ 0x00 :: Enabled,
+ 0x01 :: CommandsOnly,
+ 0x02 :: Hidden
+);
+
+proto_varint_enum!(ClientMainHand,
+ 0x00 :: Left,
+ 0x01 :: Right
+);
+
+proto_byte_flag!(ClientDisplayedSkinParts,
+ 0x01 :: cape_enabled,
+ 0x02 :: jacket_enabled,
+ 0x04 :: left_sleeve_enabled,
+ 0x08 :: right_sleeve_enabled,
+ 0x10 :: left_pants_leg_enabled,
+ 0x20 :: right_pant_legs_enabled,
+ 0x40 :: hat_enabled
+);
+
+proto_varint_enum!(InventoryOperationMode,
+ 0x00 :: MouseClick,
+ 0x01 :: ShiftClick,
+ 0x02 :: NumberClick,
+ 0x03 :: MiddleClick,
+ 0x04 :: DropClick,
+ 0x05 :: Drag,
+ 0x06 :: DoubleClick
+);
+
+__protocol_body_def_helper!(InteractAtSpec {
+ target_x: f32,
+ target_y: f32,
+ target_z: f32,
+ hand: Hand
+});
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum InteractKind {
+ Interact,
+ Attack,
+ InteractAt(InteractAtSpec)
+}
+
+impl InteractKind {
+ pub fn id(&self) -> VarInt {
+ use InteractKind::*;
+ match self {
+ Interact => 0x00,
+ Attack => 0x01,
+ InteractAt(_) => 0x02,
+ }.into()
+ }
+}
+
+impl Serialize for InteractKind {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let id = self.id();
+ to.serialize_other(&id)?;
+
+ use InteractKind::*;
+ match self {
+ InteractAt(body) => to.serialize_other(body),
+ _ => Ok(())
+ }
+ }
+}
+
+impl Deserialize for InteractKind {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized{ value: id, data } = VarInt::mc_deserialize(data)?;
+
+ use InteractKind::*;
+ match id.0 {
+ 0x00 => Deserialized::ok(Interact, data),
+ 0x01 => Deserialized::ok(Attack, data),
+ 0x02 => Ok(InteractAtSpec::mc_deserialize(data)?.map(move |body| InteractAt(body))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid entity interact kind id {}", other)))
+ }
+ }
+}
+
+proto_byte_flag!(ClientPlayerAbilities,
+ 0x01 :: creative,
+ 0x02 :: flying,
+ 0x04 :: fly_enabled,
+ 0x08 :: damaged_disabled
+);
+
+proto_varint_enum!(PlayerDiggingStatus,
+ 0x00 :: Started,
+ 0x01 :: Cancelled,
+ 0x02 :: Finished,
+ 0x03 :: DropStack,
+ 0x04 :: DropItem,
+ 0x05 :: ShootArrowOrFishEating,
+ 0x06 :: SwapItemInHand
+);
+
+proto_byte_enum!(DiggingFace,
+ 0x00 :: Bottom,
+ 0x01 :: Top,
+ 0x02 :: North,
+ 0x03 :: South,
+ 0x04 :: West,
+ 0x05 :: East
+);
+
+proto_varint_enum!(EntityActionKind,
+ 0x00 :: StartSneaking,
+ 0x01 :: StopSneaking,
+ 0x02 :: LeaveBed,
+ 0x03 :: StartSprinting,
+ 0x04 :: StopSprinting,
+ 0x05 :: StartJumpWithHorse,
+ 0x06 :: StopJumpWithHorse,
+ 0x07 :: OpenHorseInventory,
+ 0x08 :: StartFlyingWithElytra
+);
+
+proto_byte_flag!(SteerVehicleFlags,
+ 0x01 :: jump,
+ 0x02 :: unmount
+);
+
+proto_varint_enum!(ResourcePackStatus,
+ 0x00 :: Loaded,
+ 0x01 :: Declined,
+ 0x02 :: FailedDownload,
+ 0x03 :: Accepted
+);
+
+proto_varint_enum!(CommandBlockMode,
+ 0x00 :: Sequence,
+ 0x01 :: Auto,
+ 0x02 :: Redstone
+);
+
+proto_byte_flag!(CommandBlockFlags,
+ 0x01 :: track_output,
+ 0x02 :: conditional,
+ 0x04 :: automatic
+);
+
+proto_varint_enum!(UpdateStructureBlockAction,
+ 0x00 :: UpdateData,
+ 0x01 :: SaveStructure,
+ 0x02 :: LoadStructure,
+ 0x03 :: DetectSize
+);
+
+proto_varint_enum!(UpdateStructureBlockMode,
+ 0x00 :: Save,
+ 0x01 :: Load,
+ 0x02 :: Corner,
+ 0x03 :: Data
+);
+
+proto_varint_enum!(UpdateStructureBlockMirror,
+ 0x00 :: NoMirror,
+ 0x01 :: LeftRight,
+ 0x02 :: FrontBack
+);
+
+proto_varint_enum!(UpdateStructureBlockRotation,
+ 0x00 :: NoRotation,
+ 0x01 :: Clockwise90,
+ 0x02 :: Clockwise180,
+ 0x03 :: CounterClockwise90
+);
+
+proto_byte_flag!(UpdateStructureBlockFlags,
+ 0x01 :: ignore_entities,
+ 0x02 :: show_air,
+ 0x04 :: show_bounding_box
+);
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct RecipeSpec {
+ pub recipe: Recipe,
+ pub id: String
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub enum Recipe {
+ CraftingShapeless(RecipeCraftingShapelessSpec),
+ CraftingShaped(RecipeCraftingShapedSpec),
+ CraftingArmorDye,
+ CraftingBookCloning,
+ CraftingMapCloning,
+ CraftingMapExtending,
+ CraftingFireworkRocket,
+ CraftingFireworkStar,
+ CraftingFireworkStarFade,
+ CraftingRepairItem,
+ CraftingTippedArrow,
+ CraftingBannerDuplicate,
+ CraftingBannerAddPattern,
+ CraftingShieldDecoration,
+ CraftingShulkerBoxColoring,
+ CraftingSuspiciousStew,
+ Smelting(RecipeSmeltingSpec),
+ Blasting(RecipeSmeltingSpec),
+ Smoking(RecipeSmeltingSpec),
+ CampfireCooking(RecipeSmeltingSpec),
+ StoneCutting(RecipeStonecuttingSpec)
+}
+
+impl Recipe {
+ pub fn id(&self) -> String {
+ use Recipe::*;
+ match self {
+ CraftingShapeless(_) => "minecraft:crafting_shapeless",
+ CraftingShaped(_) => "minecraft:crafting_shaped",
+ CraftingArmorDye => "minecraft:crafting_special_armordye",
+ CraftingBookCloning => "minecraft:crafting_special_bookcloning",
+ CraftingMapCloning => "minecraft:crafting_special_mapcloning",
+ CraftingMapExtending => "minecraft:crafting_special_mapextending",
+ CraftingFireworkRocket => "minecraft:crafting_special_firework_rocket",
+ CraftingFireworkStar => "minecraft:crafting_special_firework_star",
+ CraftingFireworkStarFade => "minecraft:crafting_special_firework_star_fade",
+ CraftingRepairItem => "minecraft:crafting_special_repairitem",
+ CraftingTippedArrow => "minecraft:crafting_special_tippedarrow",
+ CraftingBannerDuplicate => "minecraft:crafting_special_bannerduplicate",
+ CraftingBannerAddPattern => "minecraft:crafting_special_banneraddpattern",
+ CraftingShieldDecoration => "minecraft:crafting_special_shielddecoration",
+ CraftingShulkerBoxColoring => "minecraft:crafting_special_shulkerboxcoloring",
+ CraftingSuspiciousStew => "minecraft:crafting_special_suspiciousstew",
+ Smelting(_) => "minecraft:smelting",
+ Blasting(_) => "minecraft:blasting",
+ Smoking(_) => "minecraft:smoking",
+ CampfireCooking(_) => "minecraft:campfire_cooking",
+ StoneCutting(_) => "minecraft:stonecutting",
+ }.to_owned()
+ }
+
+ fn serialize_body<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ use Recipe::*;
+ match self {
+ CraftingShapeless(body) => to.serialize_other(body),
+ CraftingShaped(body) => to.serialize_other(body),
+ Smelting(body) => to.serialize_other(body),
+ Blasting(body) => to.serialize_other(body),
+ Smoking(body) => to.serialize_other(body),
+ CampfireCooking(body) => to.serialize_other(body),
+ StoneCutting(body) => to.serialize_other(body),
+ _ => Ok(())
+ }
+ }
+}
+
+impl Serialize for RecipeSpec {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ let _type = self.recipe.id();
+ to.serialize_other(&_type)?;
+ to.serialize_other(&self.id)?;
+ self.recipe.serialize_body(to)
+ }
+}
+
+impl Deserialize for RecipeSpec {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: _type, data } = String::mc_deserialize(data)?;
+ let Deserialized { value: recipe_id, data } = String::mc_deserialize(data)?;
+
+ use Recipe::*;
+ Ok(match _type.as_str() {
+ "minecraft:crafting_shapeless" => Ok(RecipeCraftingShapelessSpec::mc_deserialize(data)?.map(move |b| CraftingShapeless(b))),
+ "minecraft:crafting_shaped" => Ok(RecipeCraftingShapedSpec::mc_deserialize(data)?.map(move |b| CraftingShaped(b))),
+ "minecraft:crafting_special_armordye" => Deserialized::ok(CraftingArmorDye, data),
+ "minecraft:crafting_special_bookcloning" => Deserialized::ok(CraftingBookCloning, data),
+ "minecraft:crafting_special_mapcloning" => Deserialized::ok(CraftingMapCloning, data),
+ "minecraft:crafting_special_mapextending" => Deserialized::ok(CraftingMapExtending, data),
+ "minecraft:crafting_special_firework_rocket" => Deserialized::ok(CraftingFireworkRocket, data),
+ "minecraft:crafting_special_firework_star" => Deserialized::ok(CraftingFireworkStar, data),
+ "minecraft:crafting_special_firework_star_fade" => Deserialized::ok(CraftingFireworkStarFade, data),
+ "minecraft:crafting_special_repairitem" => Deserialized::ok(CraftingRepairItem, data),
+ "minecraft:crafting_special_tippedarrow" => Deserialized::ok(CraftingTippedArrow, data),
+ "minecraft:crafting_special_bannerduplicate" => Deserialized::ok(CraftingBannerDuplicate, data),
+ "minecraft:crafting_special_banneraddpattern" => Deserialized::ok(CraftingBannerAddPattern, data),
+ "minecraft:crafting_special_shielddecoration" => Deserialized::ok(CraftingShieldDecoration, data),
+ "minecraft:crafting_special_shulkerboxcoloring" => Deserialized::ok(CraftingShulkerBoxColoring, data),
+ "minecraft:crafting_special_suspiciousstew" => Deserialized::ok(CraftingSuspiciousStew, data),
+ "minecraft:smelting" => Ok(RecipeSmeltingSpec::mc_deserialize(data)?.map(move |b| Smelting(b))),
+ "minecraft:blasting" => Ok(RecipeSmeltingSpec::mc_deserialize(data)?.map(move |b| Blasting(b))),
+ "minecraft:smoking" => Ok(RecipeSmeltingSpec::mc_deserialize(data)?.map(move |b| Smoking(b))),
+ "minecraft:campfire_cooking" => Ok(RecipeSmeltingSpec::mc_deserialize(data)?.map(move |b| CampfireCooking(b))),
+ "minecraft:stonecutting" => Ok(RecipeStonecuttingSpec::mc_deserialize(data)?.map(move |b| StoneCutting(b))),
+ other => Err(DeserializeErr::CannotUnderstandValue(format!("invalid crafting recipe kind {:?}", other)))
+ }?.map(move |recipe_body| {
+ RecipeSpec{ id: recipe_id, recipe: recipe_body }
+ }))
+ }
+}
+
+__protocol_body_def_helper!(RecipeIngredient {
+ items: VarIntCountedArray<Option<Slot>>
+});
+
+__protocol_body_def_helper!(RecipeCraftingShapelessSpec {
+ group: String,
+ ingredients: VarIntCountedArray<RecipeIngredient>,
+ result: Option<Slot>
+});
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct RecipeCraftingShapedSpec {
+ pub width: VarInt,
+ pub height: VarInt,
+ pub group: String,
+ pub ingredients: Vec<RecipeIngredient>,
+ pub result: Option<Slot>,
+}
+
+impl Serialize for RecipeCraftingShapedSpec {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&self.width)?;
+ to.serialize_other(&self.height)?;
+ to.serialize_other(&self.group)?;
+ for elem in &self.ingredients {
+ to.serialize_other(elem)?;
+ }
+ to.serialize_other(&self.result)?;
+ Ok(())
+ }
+}
+impl Deserialize for RecipeCraftingShapedSpec {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: width, data } = <VarInt>::mc_deserialize(data)?;
+ let Deserialized { value: height, data } = <VarInt>::mc_deserialize(data)?;
+ let Deserialized { value: group, mut data } = <String>::mc_deserialize(data)?;
+
+ let ingredients_count = width.0 as usize * height.0 as usize;
+ let mut ingredients: Vec<RecipeIngredient> = Vec::with_capacity(ingredients_count);
+ for _ in 0..ingredients_count {
+ let Deserialized { value: elem, data: rest } = RecipeIngredient::mc_deserialize(data)?;
+ data = rest;
+ ingredients.push(elem);
+ }
+
+ let Deserialized { value: result, data } = <Option<Slot>>::mc_deserialize(data)?;
+
+ Deserialized::ok(Self { width, height, group, ingredients, result }, data)
+ }
+}
+
+__protocol_body_def_helper!(RecipeSmeltingSpec {
+ group: String,
+ ingredient: RecipeIngredient,
+ result: Option<Slot>,
+ experience: f32,
+ cooking_time: VarInt
+});
+
+__protocol_body_def_helper!(RecipeStonecuttingSpec {
+ group: String,
+ ingredient: RecipeIngredient,
+ result: Option<Slot>
+});
+
+proto_varint_enum!(RecipeUnlockAction,
+ 0x00 :: Init,
+ 0x01 :: Add,
+ 0x02 :: Remove
+);
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct ChunkData {
+ pub chunk_x: i32,
+ pub chunk_z: i32,
+ pub primary_bit_mask: VarInt,
+ pub heightmaps: NamedNbtTag,
+ pub biomes: Option<[i32; 1024]>,
+ pub data: VarIntCountedArray<u8>,
+ pub block_entities: Vec<NamedNbtTag>
+}
+
+impl Serialize for ChunkData {
+ fn mc_serialize<S: Serializer>(&self, to: &mut S) -> SerializeResult {
+ to.serialize_other(&self.chunk_x)?;
+ to.serialize_other(&self.chunk_z)?;
+ let full_chunk = self.biomes.is_some();
+ to.serialize_other(&full_chunk)?;
+ to.serialize_other(&self.primary_bit_mask)?;
+ to.serialize_other(&self.heightmaps)?;
+
+ if full_chunk {
+ let biomes = self.biomes.as_ref().unwrap();
+ for elem in biomes {
+ to.serialize_other(elem)?;
+ }
+ }
+
+ to.serialize_other(&self.data)?;
+ let num_block_entities = VarInt(self.block_entities.len() as i32);
+ to.serialize_other(&num_block_entities)?;
+ for entity in &self.block_entities {
+ to.serialize_other(entity)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl Deserialize for ChunkData {
+ fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> {
+ let Deserialized { value: chunk_x, data } = i32::mc_deserialize(data)?;
+ let Deserialized { value: chunk_z, data } = i32::mc_deserialize(data)?;
+ let Deserialized { value: is_full_chunk, data } = bool::mc_deserialize(data)?;
+ let Deserialized { value: primary_bit_mask, data } = VarInt::mc_deserialize(data)?;
+ let Deserialized { value: heightmaps, mut data } = NamedNbtTag::mc_deserialize(data)?;
+ let biomes = if is_full_chunk {
+ let mut biomes: [i32; 1024] = [0i32; 1024];
+ for elem in &mut biomes {
+ let Deserialized { value, data: rest } = i32::mc_deserialize(data)?;
+ data = rest;
+ *elem = value;
+ }
+ Some(biomes)
+ } else {
+ None
+ };
+ let Deserialized { value: chunk_data, data } = VarIntCountedArray::<u8>::mc_deserialize(data)?;
+ let Deserialized { value: n_block_entities_raw, mut data } = VarInt::mc_deserialize(data)?;
+ let n_block_entities = n_block_entities_raw.0 as usize;
+ let mut block_entities = Vec::with_capacity(n_block_entities);
+ for _ in 0..n_block_entities {
+ let Deserialized { value: entity, data: rest } = NamedNbtTag::mc_deserialize(data)?;
+ data = rest;
+ block_entities.push(entity);
+ }
+
+ Deserialized::ok(ChunkData{
+ chunk_x,
+ chunk_z,
+ primary_bit_mask,
+ heightmaps,
+ biomes,
+ data: chunk_data,
+ block_entities,
+ }, data)
+ }
+} \ No newline at end of file