diff options
author | Joey Sacchini <joey@sacchini.net> | 2020-09-29 15:36:00 -0400 |
---|---|---|
committer | Joey Sacchini <joey@sacchini.net> | 2020-09-29 15:36:00 -0400 |
commit | 5b8e64e398ce5cc3cd8067545836416485b1f7ea (patch) | |
tree | c1e45b41b56e8da4984a0fbf96797ffb2b914743 /src | |
download | mcproto-rs-5b8e64e398ce5cc3cd8067545836416485b1f7ea.tar.gz mcproto-rs-5b8e64e398ce5cc3cd8067545836416485b1f7ea.tar.bz2 mcproto-rs-5b8e64e398ce5cc3cd8067545836416485b1f7ea.zip |
init commit
Diffstat (limited to 'src')
-rw-r--r-- | src/deserialize.rs | 106 | ||||
-rw-r--r-- | src/lib.rs | 19 | ||||
-rw-r--r-- | src/nbt.rs | 534 | ||||
-rw-r--r-- | src/protocol.rs | 349 | ||||
-rw-r--r-- | src/serialize.rs | 24 | ||||
-rw-r--r-- | src/status.rs | 115 | ||||
-rw-r--r-- | src/testdata/bigtest.nbt | bin | 0 -> 507 bytes | |||
-rw-r--r-- | src/types.rs | 1020 | ||||
-rw-r--r-- | src/utils.rs | 132 | ||||
-rw-r--r-- | src/uuid.rs | 185 | ||||
-rw-r--r-- | src/v1_15_2.rs | 2391 |
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 Binary files differnew file mode 100644 index 0000000..dc3769b --- /dev/null +++ b/src/testdata/bigtest.nbt 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 |