aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Cargo.toml20
-rw-r--r--src/deserialize.rs106
-rw-r--r--src/lib.rs19
-rw-r--r--src/nbt.rs534
-rw-r--r--src/protocol.rs349
-rw-r--r--src/serialize.rs24
-rw-r--r--src/status.rs115
-rw-r--r--src/testdata/bigtest.nbtbin0 -> 507 bytes
-rw-r--r--src/types.rs1020
-rw-r--r--src/utils.rs132
-rw-r--r--src/uuid.rs185
-rw-r--r--src/v1_15_2.rs2391
13 files changed, 4899 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f859244
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+Cargo.lock
+/.idea
+*.iml \ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..064313f
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "mc-proto-rs"
+version = "0.1.0"
+authors = ["Joey Sacchini <joey@sacchini.net>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+serde_json = "1.0"
+regex = "1"
+lazy_static = "1.4"
+rand = "0.7"
+flate2 = "1.0.17"
+base64 = "0.12.3"
+paste = "1.0.1"
+
+[dependencies.serde]
+version = "1.0.116"
+features = [ "derive" ] \ No newline at end of file
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, fro