From d385deb1bb5adf74fc88a51e9610e130ac34e807 Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Wed, 3 May 2023 21:47:07 -0600 Subject: [PATCH] Implement more of the protocol --- Cargo.lock | 35 +- Cargo.toml | 17 +- crates/composition-parsing/Cargo.toml | 13 + crates/composition-parsing/src/error.rs | 16 + crates/composition-parsing/src/lib.rs | 78 ++ crates/composition-parsing/src/parsable.rs | 372 ++++++ crates/composition-protocol/Cargo.toml | 13 +- crates/composition-protocol/src/blocks/mod.rs | 35 + .../composition-protocol/src/entities/cat.rs | 16 + .../composition-protocol/src/entities/frog.rs | 14 + .../src/entities/metadata.rs | 91 ++ .../composition-protocol/src/entities/mod.rs | 137 +++ .../src/entities/particle.rs | 132 ++ .../src/entities/player.rs | 24 + .../src/entities/sniffer.rs | 19 + .../src/entities/villager.rs | 41 + .../composition-protocol/src/inventory/mod.rs | 1092 +++++++++++++++++ .../src/inventory/slot.rs | 13 + crates/composition-protocol/src/lib.rs | 42 +- crates/composition-protocol/src/mctypes.rs | 106 ++ .../src/packet/clientbound/login.rs | 192 --- .../src/packet/clientbound/play.rs | 358 ------ .../src/packet/clientbound/status.rs | 37 - .../src/packet/serverbound/handshake.rs | 51 - .../src/packet/serverbound/login.rs | 115 -- .../src/packet/serverbound/play.rs | 190 --- .../src/packet/serverbound/status.rs | 32 - .../src/packets/clientbound/login.rs | 164 +++ .../{packet => packets}/clientbound/mod.rs | 0 .../src/packets/clientbound/play.rs | 283 +++++ .../src/packets/clientbound/status.rs | 33 + .../src/{packet => packets}/mod.rs | 76 +- .../src/packets/serverbound/handshake.rs | 50 + .../src/packets/serverbound/login.rs | 118 ++ .../{packet => packets}/serverbound/mod.rs | 0 .../src/packets/serverbound/play.rs | 140 +++ .../src/packets/serverbound/status.rs | 28 + crates/composition-protocol/src/util.rs | 251 ---- crates/composition-world/Cargo.toml | 13 + crates/composition-world/src/chunks.rs | 44 + .../composition-world/src/generators/mod.rs | 1 + crates/composition-world/src/lib.rs | 54 + src/config.rs | 2 + src/lib.rs | 2 +- src/net.rs | 40 +- src/server/mod.rs | 28 +- 46 files changed, 3276 insertions(+), 1332 deletions(-) create mode 100644 crates/composition-parsing/Cargo.toml create mode 100644 crates/composition-parsing/src/error.rs create mode 100644 crates/composition-parsing/src/lib.rs create mode 100644 crates/composition-parsing/src/parsable.rs create mode 100644 crates/composition-protocol/src/blocks/mod.rs create mode 100644 crates/composition-protocol/src/entities/cat.rs create mode 100644 crates/composition-protocol/src/entities/frog.rs create mode 100644 crates/composition-protocol/src/entities/metadata.rs create mode 100644 crates/composition-protocol/src/entities/mod.rs create mode 100644 crates/composition-protocol/src/entities/particle.rs create mode 100644 crates/composition-protocol/src/entities/player.rs create mode 100644 crates/composition-protocol/src/entities/sniffer.rs create mode 100644 crates/composition-protocol/src/entities/villager.rs create mode 100644 crates/composition-protocol/src/inventory/mod.rs create mode 100644 crates/composition-protocol/src/inventory/slot.rs create mode 100644 crates/composition-protocol/src/mctypes.rs delete mode 100644 crates/composition-protocol/src/packet/clientbound/login.rs delete mode 100644 crates/composition-protocol/src/packet/clientbound/play.rs delete mode 100644 crates/composition-protocol/src/packet/clientbound/status.rs delete mode 100644 crates/composition-protocol/src/packet/serverbound/handshake.rs delete mode 100644 crates/composition-protocol/src/packet/serverbound/login.rs delete mode 100644 crates/composition-protocol/src/packet/serverbound/play.rs delete mode 100644 crates/composition-protocol/src/packet/serverbound/status.rs create mode 100644 crates/composition-protocol/src/packets/clientbound/login.rs rename crates/composition-protocol/src/{packet => packets}/clientbound/mod.rs (100%) create mode 100644 crates/composition-protocol/src/packets/clientbound/play.rs create mode 100644 crates/composition-protocol/src/packets/clientbound/status.rs rename crates/composition-protocol/src/{packet => packets}/mod.rs (61%) create mode 100644 crates/composition-protocol/src/packets/serverbound/handshake.rs create mode 100644 crates/composition-protocol/src/packets/serverbound/login.rs rename crates/composition-protocol/src/{packet => packets}/serverbound/mod.rs (100%) create mode 100644 crates/composition-protocol/src/packets/serverbound/play.rs create mode 100644 crates/composition-protocol/src/packets/serverbound/status.rs delete mode 100644 crates/composition-protocol/src/util.rs create mode 100644 crates/composition-world/Cargo.toml create mode 100644 crates/composition-world/src/chunks.rs create mode 100644 crates/composition-world/src/generators/mod.rs create mode 100644 crates/composition-world/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 774ea2a..468433e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,17 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -153,6 +164,7 @@ version = "0.1.0" dependencies = [ "base64", "clap", + "composition-parsing", "composition-protocol", "once_cell", "serde", @@ -165,17 +177,38 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "composition-parsing" +version = "0.1.0" +dependencies = [ + "byteorder", + "serde_json", + "thiserror", + "tracing", +] + [[package]] name = "composition-protocol" version = "0.1.0" dependencies = [ "anyhow", "byteorder", - "serde_json", + "composition-parsing", + "serde", "thiserror", "tracing", ] +[[package]] +name = "composition-world" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "composition-protocol", + "thiserror", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" diff --git a/Cargo.toml b/Cargo.toml index 29a946d..3091b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,17 @@ members = ["crates/*"] [workspace.dependencies] +anyhow = "1.0.71" +apecs = "0.7.0" +async-trait = "0.1.68" +byteorder = "1.4.3" +composition-parsing = { path = "./crates/composition-parsing" } composition-protocol = { path = "./crates/composition-protocol" } +composition-world = { path = "./crates/composition-world" } +serde = { version = "1.0.160", features = ["serde_derive"] } +serde_json = "1.0.96" +thiserror = "1.0.40" +tokio = { version = "1.28.0", features = ["full"] } tracing = { version = "0.1.37", features = ["log"] } [package] @@ -17,11 +27,12 @@ build = "build.rs" [dependencies] base64 = "0.21.0" clap = { version = "4.2.7", features = ["derive"] } +composition-parsing = { workspace = true } composition-protocol = { workspace = true } once_cell = "1.17.1" -serde = { version = "1.0.160", features = ["serde_derive"] } -serde_json = "1.0.96" -tokio = { version = "1.28.0", features = ["full"] } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } tokio-util = "0.7.8" toml = "0.7.3" tracing = { workspace = true } diff --git a/crates/composition-parsing/Cargo.toml b/crates/composition-parsing/Cargo.toml new file mode 100644 index 0000000..08b9057 --- /dev/null +++ b/crates/composition-parsing/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "composition-parsing" +version = "0.1.0" +edition = "2021" +authors = ["Garen Tyler "] +description = "Useful shared parsing functions" +license = "MIT" + +[dependencies] +byteorder = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } diff --git a/crates/composition-parsing/src/error.rs b/crates/composition-parsing/src/error.rs new file mode 100644 index 0000000..ec54bd7 --- /dev/null +++ b/crates/composition-parsing/src/error.rs @@ -0,0 +1,16 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("invalid syntax")] + Syntax, + #[error("unexpected end of file")] + Eof, + #[error("VarInt was more than 5 bytes")] + VarIntTooLong, + #[error(transparent)] + InvalidJson(#[from] serde_json::Error), + #[error("custom error: {0}")] + Message(String), +} + +pub type Result = std::result::Result; +pub type ParseResult<'data, T> = Result<(&'data [u8], T)>; diff --git a/crates/composition-parsing/src/lib.rs b/crates/composition-parsing/src/lib.rs new file mode 100644 index 0000000..4a5ff20 --- /dev/null +++ b/crates/composition-parsing/src/lib.rs @@ -0,0 +1,78 @@ +pub mod error; +pub mod parsable; + +pub use error::{Error, ParseResult, Result}; +pub use parsable::Parsable; +pub use serde_json; + +pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]> { + move |data| { + use std::cmp::Ordering; + + match data.len().cmp(&num) { + Ordering::Greater => Ok((&data[num..], &data[..num])), + Ordering::Equal => Ok((&[], data)), + Ordering::Less => Err(Error::Eof), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] +pub struct VarInt(i32); +impl std::ops::Deref for VarInt { + type Target = i32; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for VarInt { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl From for VarInt { + fn from(value: i32) -> Self { + VarInt(value) + } +} +impl From for i32 { + fn from(value: VarInt) -> Self { + *value + } +} +impl From for VarInt { + fn from(value: usize) -> Self { + (value as i32).into() + } +} +impl std::fmt::Display for VarInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ClientState { + Handshake, + Status, + Login, + Play, + Disconnected, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn take_bytes_works() { + let data: [u8; 5] = [0, 1, 2, 3, 4]; + + assert_eq!(take_bytes(3)(&data).unwrap(), (&data[3..], &data[..3])); + assert_eq!(take_bytes(1)(&data).unwrap().0.len(), data.len() - 1); + assert_eq!(take_bytes(1)(&data).unwrap().0[0], 1); + assert_eq!(take_bytes(1)(&[0, 1]).unwrap().0.len(), 1); + assert_eq!(take_bytes(1)(&[1]).unwrap().0.len(), 0); + assert!(take_bytes(1)(&[]).is_err()); + } +} diff --git a/crates/composition-parsing/src/parsable.rs b/crates/composition-parsing/src/parsable.rs new file mode 100644 index 0000000..6f5fde0 --- /dev/null +++ b/crates/composition-parsing/src/parsable.rs @@ -0,0 +1,372 @@ +use crate::{take_bytes, Error, ParseResult, VarInt}; +use byteorder::{BigEndian, ReadBytesExt}; + +pub trait Parsable { + fn parse(data: &[u8]) -> ParseResult<'_, Self> + where + Self: Sized; + fn serialize(&self) -> Vec; + + fn parse_optional(data: &[u8]) -> ParseResult<'_, Option> + where + Self: Sized, + { + let (data, exists) = bool::parse(data)?; + if exists { + let (data, thing) = Self::parse(data)?; + Ok((data, Some(thing))) + } else { + Ok((data, None)) + } + } + fn parse_repeated(num: usize, mut data: &[u8]) -> ParseResult<'_, Vec> + where + Self: Sized, + { + let mut output = vec![]; + for _ in 0..num { + let (d, item) = Self::parse(data)?; + data = d; + output.push(item); + } + Ok((data, output)) + } + fn parse_vec(data: &[u8]) -> ParseResult<'_, Vec> + where + Self: Sized, + { + let (data, vec_len) = VarInt::parse(data)?; + Self::parse_repeated(*vec_len as usize, data) + } +} +impl Parsable for Option { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, exists) = bool::parse(data)?; + if exists { + let (data, thing) = T::parse(data)?; + Ok((data, Some(thing))) + } else { + Ok((data, None)) + } + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + match self { + Some(t) => { + let mut output = vec![]; + output.extend(true.serialize()); + output.extend(t.serialize()); + output + } + None => false.serialize(), + } + } +} +impl Parsable for Vec { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + T::parse_vec(data) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut output = vec![]; + output.extend(VarInt::from(self.len()).serialize()); + for item in self { + output.extend(item.serialize()); + } + output + } +} + +impl Parsable for serde_json::Value { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, json) = String::parse(data)?; + let json = serde_json::from_str(&json)?; + Ok((data, json)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + serde_json::to_string(self).expect("valid json").serialize() + } +} +impl Parsable for VarInt { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let mut output = 0u32; + let mut bytes_read = 0; + + for i in 0..=5 { + if i == 5 { + // VarInts can only have 5 bytes maximum. + return Err(Error::VarIntTooLong); + } else if data.len() <= i { + return Err(Error::Eof); + } + + let byte = data[i]; + output |= ((byte & 0x7f) as u32) << (7 * i); + + if byte & 0x80 != 0x80 { + // We found the last byte of the VarInt. + bytes_read = i + 1; + break; + } + } + + Ok((&data[bytes_read..], VarInt(output as i32))) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut value = self.0 as u32; + let mut output = vec![]; + loop { + let data = (value & 0x7f) as u8; + value >>= 7; + + if value == 0 { + output.push(data); + break; + } else { + output.push(data | 0x80); + } + } + output + } +} +impl Parsable for String { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, len) = VarInt::parse(data)?; + let (data, str_bytes) = take_bytes(*len as usize)(data)?; + let s = String::from_utf8_lossy(str_bytes).to_string(); + Ok((data, s)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut output = vec![]; + output.extend(VarInt::from(self.len()).serialize()); + output.extend(self.as_bytes()); + output + } +} +impl Parsable for u8 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(1)(data)?; + let i = bytes.read_u8().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for i8 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(1)(data)?; + let i = bytes.read_i8().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for u16 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(2)(data)?; + let i = bytes.read_u16::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for i16 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(2)(data)?; + let i = bytes.read_i16::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for u32 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(4)(data)?; + let i = bytes.read_u32::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for i32 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(4)(data)?; + let i = bytes.read_i32::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for u64 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(8)(data)?; + let i = bytes.read_u64::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for i64 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(8)(data)?; + let i = bytes.read_i64::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for u128 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(16)(data)?; + let i = bytes.read_u128::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for i128 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(16)(data)?; + let i = bytes.read_i128::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for f32 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(4)(data)?; + let i = bytes.read_f32::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for f64 { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, mut bytes) = take_bytes(8)(data)?; + let i = bytes.read_f64::().map_err(|_| Error::Eof)?; + Ok((data, i)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + self.to_be_bytes().to_vec() + } +} +impl Parsable for bool { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, bytes) = take_bytes(1)(data)?; + Ok((data, bytes[0] > 0x00)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + if *self { + vec![0x01] + } else { + vec![0x00] + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_varints() -> Vec<(i32, Vec)> { + vec![ + (0, vec![0x00]), + (1, vec![0x01]), + (2, vec![0x02]), + (16, vec![0x10]), + (127, vec![0x7f]), + (128, vec![0x80, 0x01]), + (255, vec![0xff, 0x01]), + (25565, vec![0xdd, 0xc7, 0x01]), + (2097151, vec![0xff, 0xff, 0x7f]), + (2147483647, vec![0xff, 0xff, 0xff, 0xff, 0x07]), + (-1, vec![0xff, 0xff, 0xff, 0xff, 0x0f]), + (-2147483648, vec![0x80, 0x80, 0x80, 0x80, 0x08]), + ] + } + #[test] + fn parse_varint_works() { + for (value, bytes) in get_varints() { + assert_eq!(value, *VarInt::parse(&bytes).unwrap().1); + } + } + #[test] + fn serialize_varint_works() { + for (value, bytes) in get_varints() { + assert_eq!(bytes, VarInt::from(value).serialize()); + } + } + + fn get_strings() -> Vec<(&'static str, Vec)> { + let s_127 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456"; + vec![ + ("", vec![0x00]), + ("A", vec![0x01, 0x41]), + ("AB", vec![0x02, 0x41, 0x42]), + (s_127, { + let mut v = vec![0x7f]; + v.extend_from_slice(s_127.as_bytes()); + v + }), + ] + } + #[test] + fn parse_string_works() { + for (value, bytes) in get_strings() { + assert_eq!(value, String::parse(&bytes).unwrap().1); + } + } + #[test] + fn serialize_string_works() { + for (value, bytes) in get_strings() { + assert_eq!(bytes, value.to_string().serialize()); + } + } +} diff --git a/crates/composition-protocol/Cargo.toml b/crates/composition-protocol/Cargo.toml index b9f6983..582de1f 100644 --- a/crates/composition-protocol/Cargo.toml +++ b/crates/composition-protocol/Cargo.toml @@ -7,8 +7,13 @@ description = "The Minecraft protocol implemented in a network-agnostic way" license = "MIT" [dependencies] -anyhow = "1.0.71" -byteorder = "1.4.3" -serde_json = "1.0.96" -thiserror = "1.0.40" +anyhow = { workspace = true } +byteorder = { workspace = true } +composition-parsing = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } tracing = { workspace = true } + +[features] +default = [] +update_1_20 = [] diff --git a/crates/composition-protocol/src/blocks/mod.rs b/crates/composition-protocol/src/blocks/mod.rs new file mode 100644 index 0000000..213522e --- /dev/null +++ b/crates/composition-protocol/src/blocks/mod.rs @@ -0,0 +1,35 @@ +pub type BlockId = &'static str; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct BlockPosition { + pub x: i32, + pub y: i32, + pub z: i32, +} +impl BlockPosition { + pub fn as_chunk_offset(&self) -> (usize, usize, usize) { + ( + (self.x % 16) as usize, + (self.y % 16) as usize, + (self.z % 16) as usize, + ) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum BlockFace { + Bottom = 0, + Top = 1, + #[default] + North = 2, + South = 3, + West = 4, + East = 5, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum Block { + #[default] + Air, + // TODO +} diff --git a/crates/composition-protocol/src/entities/cat.rs b/crates/composition-protocol/src/entities/cat.rs new file mode 100644 index 0000000..f3b51a2 --- /dev/null +++ b/crates/composition-protocol/src/entities/cat.rs @@ -0,0 +1,16 @@ +use crate::mctypes::VarInt; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Cat { + pub variant: CatVariant, + pub is_lying: bool, + pub is_relaxed: bool, + pub collar_color: VarInt, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum CatVariant { + #[default] + Black, + // TODO: Add more cat variants +} diff --git a/crates/composition-protocol/src/entities/frog.rs b/crates/composition-protocol/src/entities/frog.rs new file mode 100644 index 0000000..d0ce933 --- /dev/null +++ b/crates/composition-protocol/src/entities/frog.rs @@ -0,0 +1,14 @@ +use super::EntityId; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Frog { + pub variant: FrogVariant, + pub target: EntityId, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum FrogVariant { + #[default] + Temperate, + // TODO: Add more frog variants +} diff --git a/crates/composition-protocol/src/entities/metadata.rs b/crates/composition-protocol/src/entities/metadata.rs new file mode 100644 index 0000000..53894d8 --- /dev/null +++ b/crates/composition-protocol/src/entities/metadata.rs @@ -0,0 +1,91 @@ +use crate::{ + blocks::BlockFace, + mctypes::{Chat, Position, Uuid, VarInt}, +}; + +pub type EntityMetadata = Vec; + +#[derive(Debug, Clone, PartialEq)] +pub struct EntityMetadataEntry { + pub index: u8, + pub kind: EntityMetadataEntryKind, +} + +#[repr(u8)] +#[derive(Debug, Clone, PartialEq)] +pub enum EntityMetadataEntryKind { + Byte(u8) = 0, + VarInt(VarInt) = 1, + // TODO: Add VarLong type + VarLong(VarInt) = 2, + Float(f32) = 3, + String(String) = 4, + Chat(Chat) = 5, + OptionalChat(Option) = 6, + // TODO: Add Slot type + Slot(()) = 7, + Boolean(bool) = 8, + Rotation { + x: f32, + y: f32, + z: f32, + } = 9, + Position(Position) = 10, + OptionalPosition(Option) = 11, + Direction(BlockFace) = 12, + OptionalUuid(Uuid) = 13, + BlockId(VarInt) = 14, + // 0 or None means air + OptionalBlockId(Option) = 15, + // TODO: Add NBT type + Nbt(()) = 16, + // TODO: Add Particle type + Particle(()) = 17, + VillagerData { + biome: super::villager::VillagerBiome, + profession: super::villager::VillagerProfession, + level: VarInt, + } = 18, + // Used for entity ids + OptionalVarInt(VarInt) = 19, + Pose(EntityPose) = 20, + CatVariant(super::cat::CatVariant) = 21, + FrogVariant(super::frog::FrogVariant) = 22, + // TODO: Add dimension id + OptionalGlobalPosition((), Position) = 23, + // TODO: Add painting variant + PaintingVariant(()) = 24, + #[cfg(feature = "update_1_20")] + SnifferState(super::sniffer::SnifferState) = 25, + Vector3 { + x: f32, + y: f32, + z: f32, + } = 26, + Quaternion { + x: f32, + y: f32, + z: f32, + w: f32, + } = 27, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum EntityPose { + #[default] + Standing = 0, + FallFlying = 1, + Sleeping = 2, + Swimming = 3, + SpinAttack = 4, + Sneaking = 5, + LongJumping = 6, + Dying = 7, + Croaking = 8, + UsingTongue = 9, + Sitting = 10, + Roaring = 11, + Sniffing = 12, + Emerging = 13, + Digging = 14, +} diff --git a/crates/composition-protocol/src/entities/mod.rs b/crates/composition-protocol/src/entities/mod.rs new file mode 100644 index 0000000..a5061e6 --- /dev/null +++ b/crates/composition-protocol/src/entities/mod.rs @@ -0,0 +1,137 @@ +pub mod cat; +pub mod frog; +pub mod metadata; +pub mod particle; +pub mod player; +#[cfg(feature = "update_1_20")] +pub mod sniffer; +pub mod villager; + +use crate::{ + blocks::BlockPosition, + mctypes::{Chat, Uuid, VarInt}, +}; +use composition_parsing::{Parsable, ParseResult}; + +pub type EntityId = VarInt; +pub type EntityUuid = Uuid; + +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub struct EntityPosition { + pub x: f64, + pub y: f64, + pub z: f64, +} +impl Parsable for EntityPosition { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, x) = f64::parse(data)?; + let (data, y) = f64::parse(data)?; + let (data, z) = f64::parse(data)?; + Ok((data, EntityPosition { x, y, z })) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut output = vec![]; + output.extend(self.x.serialize()); + output.extend(self.y.serialize()); + output.extend(self.z.serialize()); + output + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub struct EntityRotation { + pub pitch: u8, + pub yaw: u8, +} +impl Parsable for EntityRotation { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, pitch) = u8::parse(data)?; + let (data, yaw) = u8::parse(data)?; + Ok((data, EntityRotation { pitch, yaw })) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut output = vec![]; + output.extend(self.pitch.serialize()); + output.extend(self.yaw.serialize()); + output + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub struct EntityVelocity { + pub x: i16, + pub y: i16, + pub z: i16, +} +impl Parsable for EntityVelocity { + #[tracing::instrument] + fn parse(data: &[u8]) -> ParseResult<'_, Self> { + let (data, x) = i16::parse(data)?; + let (data, y) = i16::parse(data)?; + let (data, z) = i16::parse(data)?; + Ok((data, EntityVelocity { x, y, z })) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut output = vec![]; + output.extend(self.x.serialize()); + output.extend(self.y.serialize()); + output.extend(self.z.serialize()); + output + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Entity { + pub position: EntityPosition, + pub velocity: EntityVelocity, + pub is_on_fire: bool, + pub is_crouching: bool, + pub is_sprinting: bool, + pub is_swimming: bool, + pub is_invisible: bool, + pub is_glowing: bool, + pub is_elytra_flying: bool, + pub custom_name: Option, +} +#[derive(Debug, Clone, PartialEq, Default)] +pub struct LivingEntity { + pub is_hand_active: bool, + pub main_hand: bool, + pub in_riptide_spin_attack: bool, + pub health: f32, + pub potion_effect_color: Option, + pub is_potion_effect_ambient: bool, + pub arrow_count: VarInt, + pub bee_stingers: VarInt, + pub currently_sleeping_bed_position: Option, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Mob { + pub has_ai: bool, + pub is_left_handed: bool, + pub is_aggressive: bool, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct PathfinderMob; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct AgeableMob { + pub is_baby: bool, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Animal; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct TameableAnimal { + pub is_sitting: bool, + pub is_tamed: bool, + pub owner: Option, +} diff --git a/crates/composition-protocol/src/entities/particle.rs b/crates/composition-protocol/src/entities/particle.rs new file mode 100644 index 0000000..b6ad7dd --- /dev/null +++ b/crates/composition-protocol/src/entities/particle.rs @@ -0,0 +1,132 @@ +use super::EntityId; +use crate::mctypes::{Position, VarInt}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Particle { + kind: ParticleKind, +} + +#[repr(u8)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum ParticleKind { + AmbientEntityEffect = 0, + AngryVillager = 1, + // TODO: Add the block state + Block(()) = 2, + // TODO: Add the block state + BlockMarker(()) = 3, + Bubble = 4, + Cloud = 5, + Crit = 6, + DamageIndicator = 7, + DragonBreath = 8, + DrippingLava = 9, + FallingLava = 10, + LandingLava = 11, + DrippingWater = 12, + FallingWater = 13, + Dust { + rgb: [f32; 3], + scale: f32, + } = 14, + DustColorTransition { + from_rgb: [f32; 3], + scale: f32, + to_rgb: [f32; 3], + } = 15, + Effect = 16, + ElderGuardian = 17, + EnchantedHit = 18, + Enchant = 19, + EndRod = 20, + EntityEffect = 21, + ExplosionEmitter = 22, + Explosion = 23, + SonicBoom = 24, + // TODO: Add the block state + FallingDust(()) = 25, + Firework = 26, + Fishing = 27, + Flame = 28, + DrippingCherryLeaves = 29, + FallingCherryLeaves = 30, + LandingCherryLeaves = 31, + SkulkSoul = 32, + SkulkCharge { + roll: f32, + } = 33, + SkulkChargePop = 34, + SoulFireFlame = 35, + Soul = 36, + Flash = 37, + HappyVillager = 38, + Composter = 39, + Heart = 40, + InstantEffect = 41, + // TODO: Add the slot + Item(()) = 42, + Vibration { + source: VibrationParticleSource, + travel_duration_ticks: VarInt, + } = 43, + ItemSlime = 44, + ItemSnowball = 45, + LargeSmoke = 46, + Lava = 47, + Mycelium = 48, + Note = 49, + Poof = 50, + Portal = 51, + Rain = 52, + Smoke = 53, + Sneeze = 54, + Spit = 55, + SquidInk = 56, + SweepAttack = 57, + TotemOfUndying = 58, + Underwater = 59, + Splash = 60, + Witch = 61, + BubblePop = 62, + CurrentDown = 63, + BubbleColumnUp = 64, + Nautilus = 65, + Dolphin = 66, + CampfireCosySmoke = 67, + CampfireSignalSmoke = 68, + DrippingHoney = 69, + FallingHoney = 70, + LandingHoney = 71, + FallingNectar = 72, + FallingSporeBlossom = 73, + Ash = 74, + CrimsonSpore = 75, + WarpedSpore = 76, + SporeBlossomAir = 77, + DrippingObsidianTear = 78, + FallingObsidianTear = 79, + LandingObsidianTear = 80, + ReversePortal = 81, + WhiteAsh = 82, + SmallFlame = 83, + Snowflake = 84, + DrippingDripstoneLava = 85, + FallingDripstoneLava = 86, + DrippingDripstoneWater = 87, + FallingDripstoneWater = 88, + GlowSquidInk = 89, + Glow = 90, + WaxOn = 91, + WaxOff = 92, + ElectricSpark = 93, + Scrape = 94, + Shriek { + delay_ticks: VarInt, + } = 95, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum VibrationParticleSource { + Block(Position), + Entity { id: EntityId, eye_height: f32 }, +} diff --git a/crates/composition-protocol/src/entities/player.rs b/crates/composition-protocol/src/entities/player.rs new file mode 100644 index 0000000..94ef485 --- /dev/null +++ b/crates/composition-protocol/src/entities/player.rs @@ -0,0 +1,24 @@ +use crate::mctypes::VarInt; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct Player { + pub additional_hearts: f32, + pub score: VarInt, + pub skin_parts: PlayerSkinParts, + pub right_handed: bool, + // TODO: NBT data + pub left_shoulder_entity: (), + // TODO: NBT data + pub right_shoulder_entity: (), +} + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct PlayerSkinParts { + pub cape_enabled: bool, + pub jacket_enabled: bool, + pub left_sleeve_enabled: bool, + pub right_sleeve_enabled: bool, + pub left_pant_leg_enabled: bool, + pub right_pant_leg_enabled: bool, + pub hat_enabled: bool, +} diff --git a/crates/composition-protocol/src/entities/sniffer.rs b/crates/composition-protocol/src/entities/sniffer.rs new file mode 100644 index 0000000..6be27b2 --- /dev/null +++ b/crates/composition-protocol/src/entities/sniffer.rs @@ -0,0 +1,19 @@ +use crate::mctypes::VarInt; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Sniffer { + pub state: SnifferState, + pub seed_drop_ticks: VarInt, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SnifferState { + #[default] + Idling = 0, + FeelingHappy = 1, + Scenting = 2, + Sniffing = 3, + Searching = 4, + Digging = 5, + Rising = 6, +} diff --git a/crates/composition-protocol/src/entities/villager.rs b/crates/composition-protocol/src/entities/villager.rs new file mode 100644 index 0000000..ff0826c --- /dev/null +++ b/crates/composition-protocol/src/entities/villager.rs @@ -0,0 +1,41 @@ +use crate::mctypes::VarInt; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Villager { + pub head_shake_ticks: VarInt, + pub biome: VillagerBiome, + pub profession: VillagerProfession, + pub level: VarInt, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum VillagerBiome { + #[default] + Desert = 0, + Jungle = 1, + Plains = 2, + Savanna = 3, + Snow = 4, + Swamp = 5, + Taiga = 6, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum VillagerProfession { + #[default] + None = 0, + Armorer = 1, + Butcher = 2, + Cartographer = 3, + Cleric = 4, + Farmer = 5, + Fisherman = 6, + Fletcher = 7, + Leatherworker = 8, + Librarian = 9, + Mason = 10, + Nitwit = 11, + Shepherd = 12, + Toolsmith = 13, + Weaponsmith = 14, +} diff --git a/crates/composition-protocol/src/inventory/mod.rs b/crates/composition-protocol/src/inventory/mod.rs new file mode 100644 index 0000000..93fd51e --- /dev/null +++ b/crates/composition-protocol/src/inventory/mod.rs @@ -0,0 +1,1092 @@ +pub mod slot; + +use slot::Slot; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum InventoryKind { + Generic9x1 = 0, + Generic9x2 = 1, + #[default] + Generic9x3 = 2, + Generic9x4 = 3, + Generic9x5 = 4, + Generic9x6 = 5, + Generic3x3 = 6, + Anvil = 7, + Beacon = 8, + BlastFurnace = 9, + BrewingStand = 10, + Crafting = 11, + Enchantment = 12, + Furnace = 13, + Grindstone = 14, + Hopper = 15, + Lectern = 16, + Loom = 17, + Merchant = 18, + ShulkerBox = 19, + #[cfg(not(feature = "update_1_20"))] + LegacySmithing = 20, + #[cfg(feature = "update_1_20")] + Smithing = 21, + Smoker = 22, + Cartography = 23, + Stonecutter = 24, +} +impl InventoryKind { + pub fn as_minecraft_id(&self) -> &'static str { + match self { + InventoryKind::Generic9x1 => "minecraft:generic_9x1", + InventoryKind::Generic9x2 => "minecraft:Generic_9x2", + InventoryKind::Generic9x3 => "minecraft:Generic_9x3", + InventoryKind::Generic9x4 => "minecraft:Generic_9x4", + InventoryKind::Generic9x5 => "minecraft:Generic_9x5", + InventoryKind::Generic9x6 => "minecraft:Generic_9x6", + InventoryKind::Generic3x3 => "minecraft:Generic_3x3", + InventoryKind::Anvil => "minecraft:anvil", + InventoryKind::Beacon => "minecraft:beacon", + InventoryKind::BlastFurnace => "minecraft:blast_furnace", + InventoryKind::BrewingStand => "minecraft:brewing_stand", + InventoryKind::Crafting => "minecraft:crafting", + InventoryKind::Enchantment => "minecraft:enchantment", + InventoryKind::Furnace => "minecraft:furnace", + InventoryKind::Grindstone => "minecraft:grindstone", + InventoryKind::Hopper => "minecraft:hopper", + InventoryKind::Lectern => "minecraft:lectern", + InventoryKind::Loom => "minecraft:loom", + InventoryKind::Merchant => "minecraft:merchant", + InventoryKind::ShulkerBox => "mincraft:shulker_box", + #[cfg(not(feature = "update_1_20"))] + InventoryKind::LegacySmithing => "minecraft:legacy_smithing", + #[cfg(feature = "update_1_20")] + InventoryKind::Smithing => "minecraft:smithing", + InventoryKind::Smoker => "minecraft:smoker", + InventoryKind::Cartography => "minecraft:cartography", + InventoryKind::Stonecutter => "minecraft:stonecutter", + } + } +} + +pub struct PlayerInventory([Slot; 46]); +impl PlayerInventory { + pub fn crafting_output(&self) -> &Slot { + &self.0[0] + } + pub fn crafting_output_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn crafting_input(&self) -> &[Slot; 4] { + <&[Slot; 4]>::try_from(&self.0[1..5]).unwrap() + } + pub fn crafting_input_mut(&mut self) -> &mut [Slot; 4] { + <&mut [Slot; 4]>::try_from(&mut self.0[1..5]).unwrap() + } + + pub fn head(&self) -> &Slot { + &self.0[5] + } + pub fn head_mut(&mut self) -> &mut Slot { + &mut self.0[5] + } + pub fn chest(&self) -> &Slot { + &self.0[6] + } + pub fn chest_mut(&mut self) -> &mut Slot { + &mut self.0[6] + } + pub fn legs(&self) -> &Slot { + &self.0[7] + } + pub fn legs_mut(&mut self) -> &mut Slot { + &mut self.0[7] + } + pub fn feet(&self) -> &Slot { + &self.0[8] + } + pub fn feet_mut(&mut self) -> &mut Slot { + &mut self.0[8] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[9..36]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[9..36]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[36..45]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[36..45]).unwrap() + } + + pub fn offhand(&self) -> &Slot { + &self.0[45] + } + pub fn offhand_mut(&mut self) -> &mut Slot { + &mut self.0[45] + } +} + +pub struct Chest([Slot; 63]); +impl Chest { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Generic9x3; + + pub fn contents(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[0..27]).unwrap() + } + pub fn contents_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[0..27]).unwrap() + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[27..54]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[27..54]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[54..63]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[54..63]).unwrap() + } +} + +pub struct LargeChest([Slot; 90]); +impl LargeChest { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Generic9x6; + + pub fn contents(&self) -> &[Slot; 54] { + <&[Slot; 54]>::try_from(&self.0[0..54]).unwrap() + } + pub fn contents_mut(&mut self) -> &mut [Slot; 54] { + <&mut [Slot; 54]>::try_from(&mut self.0[0..54]).unwrap() + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[54..81]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[54..81]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[81..90]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[81..90]).unwrap() + } +} + +pub struct CraftingTable([Slot; 46]); +impl CraftingTable { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Crafting; + + pub fn output(&self) -> &Slot { + &self.0[0] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn input(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[1..10]).unwrap() + } + pub fn input_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[1..10]).unwrap() + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[10..37]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[10..37]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[37..46]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[37..46]).unwrap() + } +} + +pub struct Furnace([Slot; 39]); +impl Furnace { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Furnace; + + pub fn ingredient(&self) -> &Slot { + &self.0[0] + } + pub fn ingredient_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn fuel(&self) -> &Slot { + &self.0[1] + } + pub fn fuel_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct BlastFurnace([Slot; 39]); +impl BlastFurnace { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::BlastFurnace; + + pub fn ingredient(&self) -> &Slot { + &self.0[0] + } + pub fn ingredient_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn fuel(&self) -> &Slot { + &self.0[1] + } + pub fn fuel_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct Smoker([Slot; 39]); +impl Smoker { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Smoker; + + pub fn ingredient(&self) -> &Slot { + &self.0[0] + } + pub fn ingredient_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn fuel(&self) -> &Slot { + &self.0[1] + } + pub fn fuel_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct Dispenser([Slot; 45]); +impl Dispenser { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Generic3x3; + + pub fn contents(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[0..9]).unwrap() + } + pub fn contents_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[0..9]).unwrap() + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[9..35]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[9..35]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[36..45]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[36..45]).unwrap() + } +} + +pub struct EnchantmentTable([Slot; 38]); +impl EnchantmentTable { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Enchantment; + + pub fn item(&self) -> &Slot { + &self.0[0] + } + pub fn item_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn lapis_lazuli(&self) -> &Slot { + &self.0[1] + } + pub fn lapis_lazuli_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[2..29]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[2..29]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[29..38]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[29..38]).unwrap() + } +} + +pub struct BrewingStand([Slot; 41]); +impl BrewingStand { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::BrewingStand; + + pub fn bottles(&self) -> &[Slot; 3] { + <&[Slot; 3]>::try_from(&self.0[0..3]).unwrap() + } + pub fn bottles_mut(&mut self) -> &mut [Slot; 3] { + <&mut [Slot; 3]>::try_from(&mut self.0[0..3]).unwrap() + } + + pub fn ingredient(&self) -> &Slot { + &self.0[3] + } + pub fn ingredient_mut(&mut self) -> &mut Slot { + &mut self.0[3] + } + + pub fn blaze_powder(&self) -> &Slot { + &self.0[4] + } + pub fn blaze_powder_mut(&mut self) -> &mut Slot { + &mut self.0[4] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[5..32]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[5..32]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[32..41]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[32..41]).unwrap() + } +} + +pub struct VillagerTrading([Slot; 39]); +impl VillagerTrading { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Merchant; + + pub fn input(&self) -> &[Slot; 2] { + <&[Slot; 2]>::try_from(&self.0[0..2]).unwrap() + } + pub fn input_mut(&mut self) -> &mut [Slot; 2] { + <&mut [Slot; 2]>::try_from(&mut self.0[0..2]).unwrap() + } + + pub fn result(&self) -> &Slot { + &self.0[2] + } + pub fn result_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct Beacon([Slot; 37]); +impl Beacon { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Beacon; + + pub fn payment(&self) -> &Slot { + &self.0[0] + } + pub fn payment_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[1..28]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[1..28]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[28..37]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[28..37]).unwrap() + } +} + +pub struct Anvil([Slot; 39]); +impl Anvil { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Anvil; + + pub fn input(&self) -> &[Slot; 2] { + <&[Slot; 2]>::try_from(&self.0[0..2]).unwrap() + } + pub fn input_mut(&mut self) -> &mut [Slot; 2] { + <&mut [Slot; 2]>::try_from(&mut self.0[0..2]).unwrap() + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct Hopper([Slot; 41]); +impl Hopper { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Hopper; + + pub fn contents(&self) -> &[Slot; 5] { + <&[Slot; 5]>::try_from(&self.0[0..5]).unwrap() + } + pub fn contents_mut(&mut self) -> &mut [Slot; 5] { + <&mut [Slot; 5]>::try_from(&mut self.0[0..5]).unwrap() + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[5..32]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[5..32]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[32..41]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[32..41]).unwrap() + } +} + +pub struct Shulker([Slot; 63]); +impl Shulker { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::ShulkerBox; + + pub fn contents(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[0..27]).unwrap() + } + pub fn contents_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[0..27]).unwrap() + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[27..54]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[27..54]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[54..63]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[54..63]).unwrap() + } +} + +pub enum Llama { + Unchested([Slot; 38]), + Strength1([Slot; 41]), + Strength2([Slot; 44]), + Strength3([Slot; 47]), + Strength4([Slot; 50]), + Strength5([Slot; 53]), +} +impl Llama { + pub fn saddle(&self) -> &Slot { + match self { + Llama::Unchested(slots) => &slots[0], + Llama::Strength1(slots) => &slots[0], + Llama::Strength2(slots) => &slots[0], + Llama::Strength3(slots) => &slots[0], + Llama::Strength4(slots) => &slots[0], + Llama::Strength5(slots) => &slots[0], + } + } + pub fn saddle_mut(&mut self) -> &mut Slot { + match self { + Llama::Unchested(slots) => &mut slots[0], + Llama::Strength1(slots) => &mut slots[0], + Llama::Strength2(slots) => &mut slots[0], + Llama::Strength3(slots) => &mut slots[0], + Llama::Strength4(slots) => &mut slots[0], + Llama::Strength5(slots) => &mut slots[0], + } + } + + pub fn carpet(&self) -> &Slot { + match self { + Llama::Unchested(slots) => &slots[1], + Llama::Strength1(slots) => &slots[1], + Llama::Strength2(slots) => &slots[1], + Llama::Strength3(slots) => &slots[1], + Llama::Strength4(slots) => &slots[1], + Llama::Strength5(slots) => &slots[1], + } + } + pub fn carpet_mut(&mut self) -> &mut Slot { + match self { + Llama::Unchested(slots) => &mut slots[1], + Llama::Strength1(slots) => &mut slots[1], + Llama::Strength2(slots) => &mut slots[1], + Llama::Strength3(slots) => &mut slots[1], + Llama::Strength4(slots) => &mut slots[1], + Llama::Strength5(slots) => &mut slots[1], + } + } + + pub fn llama_inventory_size(&self) -> usize { + match self { + Llama::Unchested(_) => 0, + Llama::Strength1(_) => 3, + Llama::Strength2(_) => 6, + Llama::Strength3(_) => 9, + Llama::Strength4(_) => 12, + Llama::Strength5(_) => 15, + } + } + fn llama_inventory_range(&self) -> std::ops::Range { + 2..(self.llama_inventory_size() + 2) + } + fn main_inventory_range(&self) -> std::ops::Range { + let start = self.llama_inventory_range().end; + let end = start + 27; + start..end + } + fn hotbar_range(&self) -> std::ops::Range { + let start = self.main_inventory_range().end; + let end = start + 9; + start..end + } + + pub fn llama_inventory(&self) -> &[Slot] { + let r = self.llama_inventory_range(); + match self { + Llama::Unchested(_) => &[], + Llama::Strength1(slots) => &slots[r], + Llama::Strength2(slots) => &slots[r], + Llama::Strength3(slots) => &slots[r], + Llama::Strength4(slots) => &slots[r], + Llama::Strength5(slots) => &slots[r], + } + } + pub fn llama_inventory_mut(&mut self) -> &mut [Slot] { + let r = self.llama_inventory_range(); + match self { + Llama::Unchested(_) => &mut [], + Llama::Strength1(slots) => &mut slots[r], + Llama::Strength2(slots) => &mut slots[r], + Llama::Strength3(slots) => &mut slots[r], + Llama::Strength4(slots) => &mut slots[r], + Llama::Strength5(slots) => &mut slots[r], + } + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + let r = self.main_inventory_range(); + let s = match self { + Llama::Unchested(_) => &[], + Llama::Strength1(slots) => &slots[r], + Llama::Strength2(slots) => &slots[r], + Llama::Strength3(slots) => &slots[r], + Llama::Strength4(slots) => &slots[r], + Llama::Strength5(slots) => &slots[r], + }; + <&[Slot; 27]>::try_from(s).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + let r = self.main_inventory_range(); + let s = match self { + Llama::Unchested(_) => &mut [], + Llama::Strength1(slots) => &mut slots[r], + Llama::Strength2(slots) => &mut slots[r], + Llama::Strength3(slots) => &mut slots[r], + Llama::Strength4(slots) => &mut slots[r], + Llama::Strength5(slots) => &mut slots[r], + }; + <&mut [Slot; 27]>::try_from(s).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + let r = self.hotbar_range(); + let s = match self { + Llama::Unchested(_) => &[], + Llama::Strength1(slots) => &slots[r], + Llama::Strength2(slots) => &slots[r], + Llama::Strength3(slots) => &slots[r], + Llama::Strength4(slots) => &slots[r], + Llama::Strength5(slots) => &slots[r], + }; + <&[Slot; 9]>::try_from(s).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + let r = self.hotbar_range(); + let s = match self { + Llama::Unchested(_) => &mut [], + Llama::Strength1(slots) => &mut slots[r], + Llama::Strength2(slots) => &mut slots[r], + Llama::Strength3(slots) => &mut slots[r], + Llama::Strength4(slots) => &mut slots[r], + Llama::Strength5(slots) => &mut slots[r], + }; + <&mut [Slot; 9]>::try_from(s).unwrap() + } +} + +pub struct Horse([Slot; 38]); +impl Horse { + pub fn saddle(&self) -> &Slot { + &self.0[0] + } + pub fn saddle_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn armor(&self) -> &Slot { + &self.0[1] + } + pub fn armor_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[2..29]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[2..29]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[29..38]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[29..38]).unwrap() + } +} + +pub enum Donkey { + Unchested([Slot; 38]), + Chested([Slot; 53]), +} +impl Donkey { + pub fn saddle(&self) -> &Slot { + match self { + Donkey::Unchested(slots) => &slots[0], + Donkey::Chested(slots) => &slots[0], + } + } + pub fn saddle_mut(&mut self) -> &mut Slot { + match self { + Donkey::Unchested(slots) => &mut slots[0], + Donkey::Chested(slots) => &mut slots[0], + } + } + + pub fn armor(&self) -> &Slot { + match self { + Donkey::Unchested(slots) => &slots[1], + Donkey::Chested(slots) => &slots[1], + } + } + pub fn armor_mut(&mut self) -> &mut Slot { + match self { + Donkey::Unchested(slots) => &mut slots[1], + Donkey::Chested(slots) => &mut slots[1], + } + } + + pub fn donkey_inventory(&self) -> Option<&[Slot; 15]> { + if let Donkey::Chested(slots) = self { + Some(<&[Slot; 15]>::try_from(&slots[2..17]).unwrap()) + } else { + None + } + } + pub fn donkey_inventory_mut(&mut self) -> Option<&mut [Slot; 15]> { + if let Donkey::Chested(slots) = self { + Some(<&mut [Slot; 15]>::try_from(&mut slots[2..17]).unwrap()) + } else { + None + } + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + let s = match self { + Donkey::Unchested(slots) => &slots[2..29], + Donkey::Chested(slots) => &slots[17..44], + }; + <&[Slot; 27]>::try_from(s).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + let s = match self { + Donkey::Unchested(slots) => &mut slots[2..29], + Donkey::Chested(slots) => &mut slots[17..44], + }; + <&mut [Slot; 27]>::try_from(s).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + let s = match self { + Donkey::Unchested(slots) => &slots[29..38], + Donkey::Chested(slots) => &slots[44..53], + }; + <&[Slot; 9]>::try_from(s).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + let s = match self { + Donkey::Unchested(slots) => &mut slots[29..38], + Donkey::Chested(slots) => &mut slots[44..53], + }; + <&mut [Slot; 9]>::try_from(s).unwrap() + } +} + +pub struct CartographyTable([Slot; 39]); +impl CartographyTable { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Cartography; + + pub fn map(&self) -> &Slot { + &self.0[0] + } + pub fn map_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn paper(&self) -> &Slot { + &self.0[1] + } + pub fn paper_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct Grindstone([Slot; 39]); +impl Grindstone { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Grindstone; + + pub fn input(&self) -> &[Slot; 2] { + <&[Slot; 2]>::try_from(&self.0[0..2]).unwrap() + } + pub fn input_mut(&mut self) -> &mut [Slot; 2] { + <&mut [Slot; 2]>::try_from(&mut self.0[0..2]).unwrap() + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +pub struct Lectern(Slot); +impl Lectern { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Lectern; + + pub fn book(&self) -> &Slot { + &self.0 + } + pub fn book_mut(&mut self) -> &mut Slot { + &mut self.0 + } +} + +pub struct Loom([Slot; 40]); +impl Loom { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Loom; + + pub fn banner(&self) -> &Slot { + &self.0[0] + } + pub fn banner_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn dye(&self) -> &Slot { + &self.0[1] + } + pub fn dye_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn pattern(&self) -> &Slot { + &self.0[2] + } + pub fn pattern_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn output(&self) -> &Slot { + &self.0[3] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[3] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[4..31]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[4..31]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[31..40]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[31..40]).unwrap() + } +} + +pub struct Stonecutter([Slot; 38]); +impl Stonecutter { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Stonecutter; + + pub fn input(&self) -> &Slot { + &self.0[0] + } + pub fn input_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn output(&self) -> &Slot { + &self.0[1] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[2..29]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[2..29]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[29..38]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[29..38]).unwrap() + } +} + +#[cfg(not(feature = "update_1_20"))] +pub struct LegacySmithingTable([Slot; 39]); +#[cfg(not(feature = "update_1_20"))] +impl LegacySmithingTable { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::LegacySmithing; + + pub fn base_item(&self) -> &Slot { + &self.0[0] + } + pub fn base_item_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn additional_item(&self) -> &Slot { + &self.0[1] + } + pub fn additional_item_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn output(&self) -> &Slot { + &self.0[2] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[3..30]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[3..30]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[30..39]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[30..39]).unwrap() + } +} + +#[cfg(feature = "update_1_20")] +pub struct SmithingTable([Slot; 40]); +#[cfg(feature = "update_1_20")] +impl SmithingTable { + pub const INVENTORY_KIND: InventoryKind = InventoryKind::Smithing; + + pub fn template(&self) -> &Slot { + &self.0[0] + } + pub fn template_mut(&mut self) -> &mut Slot { + &mut self.0[0] + } + + pub fn base_item(&self) -> &Slot { + &self.0[1] + } + pub fn base_item_mut(&mut self) -> &mut Slot { + &mut self.0[1] + } + + pub fn additional_item(&self) -> &Slot { + &self.0[2] + } + pub fn additional_item_mut(&mut self) -> &mut Slot { + &mut self.0[2] + } + + pub fn output(&self) -> &Slot { + &self.0[3] + } + pub fn output_mut(&mut self) -> &mut Slot { + &mut self.0[3] + } + + pub fn main_inventory(&self) -> &[Slot; 27] { + <&[Slot; 27]>::try_from(&self.0[4..31]).unwrap() + } + pub fn main_inventory_mut(&mut self) -> &mut [Slot; 27] { + <&mut [Slot; 27]>::try_from(&mut self.0[4..31]).unwrap() + } + + pub fn hotbar(&self) -> &[Slot; 9] { + <&[Slot; 9]>::try_from(&self.0[31..40]).unwrap() + } + pub fn hotbar_mut(&mut self) -> &mut [Slot; 9] { + <&mut [Slot; 9]>::try_from(&mut self.0[31..40]).unwrap() + } +} diff --git a/crates/composition-protocol/src/inventory/slot.rs b/crates/composition-protocol/src/inventory/slot.rs new file mode 100644 index 0000000..3bb3910 --- /dev/null +++ b/crates/composition-protocol/src/inventory/slot.rs @@ -0,0 +1,13 @@ +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct Slot { + pub contents: Option, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub struct ItemStack { + // TODO: Item ID + pub id: (), + pub count: u8, + // TODO: NBT + pub nbt: (), +} diff --git a/crates/composition-protocol/src/lib.rs b/crates/composition-protocol/src/lib.rs index 8e24fea..e3fd637 100644 --- a/crates/composition-protocol/src/lib.rs +++ b/crates/composition-protocol/src/lib.rs @@ -1,40 +1,12 @@ -pub mod packet; -pub mod util; +pub mod blocks; +pub mod entities; +pub mod inventory; +pub mod mctypes; +pub mod packets; use thiserror::Error; -pub type Json = serde_json::Value; -pub type Chat = Json; -pub type Uuid = u128; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ClientState { - Handshake, - Status, - Login, - Play, - Disconnected, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Difficulty { - Peaceful = 0, - Easy = 1, - Normal = 2, - Hard = 3, -} -impl TryFrom for Difficulty { - type Error = (); - fn try_from(value: u8) -> std::result::Result { - match value { - 0 => Ok(Difficulty::Peaceful), - 1 => Ok(Difficulty::Easy), - 2 => Ok(Difficulty::Normal), - 3 => Ok(Difficulty::Hard), - _ => Err(()), - } - } -} +pub use composition_parsing::ClientState; #[derive(Error, Debug)] pub enum ProtocolError { @@ -47,7 +19,7 @@ pub enum ProtocolError { #[error("communicating to disconnected client")] Disconnected, #[error(transparent)] - JsonError(#[from] serde_json::Error), + ParseError(#[from] composition_parsing::Error), #[error(transparent)] Other(#[from] anyhow::Error), } diff --git a/crates/composition-protocol/src/mctypes.rs b/crates/composition-protocol/src/mctypes.rs new file mode 100644 index 0000000..9bbb58d --- /dev/null +++ b/crates/composition-protocol/src/mctypes.rs @@ -0,0 +1,106 @@ +use composition_parsing::Parsable; + +pub type Uuid = u128; +pub use composition_parsing::VarInt; +pub type Json = composition_parsing::serde_json::Value; +pub type Chat = Json; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Position { + pub x: i32, + pub y: i32, + pub z: i32, +} +impl Position { + #[tracing::instrument] + pub fn new(x: i32, y: i32, z: i32) -> Self { + Position { x, y, z } + } +} +impl Parsable for Position { + #[tracing::instrument] + fn parse(data: &[u8]) -> composition_parsing::ParseResult<'_, Self> { + let (data, i) = i64::parse(data)?; + + // x: i26, z: i26, y: i12 + let x = i >> 38; + let mut y = i & 0xFFF; + if y >= 0x800 { + y -= 0x1000; + } + let z = i << 26 >> 38; + + Ok((data, Position::new(x as i32, y as i32, z as i32))) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let i: i64 = ((self.x as i64 & 0x3FF_FFFF) << 38) + | ((self.z as i64 & 0x3FF_FFFF) << 12) + | (self.y as i64 & 0xFFF); + i.serialize() + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Difficulty { + Peaceful = 0, + Easy = 1, + Normal = 2, + Hard = 3, +} +impl TryFrom for Difficulty { + type Error = (); + fn try_from(value: u8) -> std::result::Result { + match value { + 0 => Ok(Difficulty::Peaceful), + 1 => Ok(Difficulty::Easy), + 2 => Ok(Difficulty::Normal), + 3 => Ok(Difficulty::Hard), + _ => Err(()), + } + } +} +impl Parsable for Difficulty { + #[tracing::instrument] + fn parse(data: &[u8]) -> composition_parsing::ParseResult<'_, Self> { + let (data, difficulty) = u8::parse(data)?; + let difficulty: Difficulty = difficulty + .try_into() + .expect("TODO: handle invalid difficulty"); + Ok((data, difficulty)) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + vec![*self as u8] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn get_positions() -> Vec<(Position, Vec)> { + vec![ + // x: 01000110000001110110001100 z: 10110000010101101101001000 y: 001100111111 + ( + Position::new(18357644, 831, -20882616), + vec![ + 0b01000110, 0b00000111, 0b01100011, 0b00101100, 0b00010101, 0b10110100, + 0b10000011, 0b00111111, + ], + ), + ] + } + #[test] + fn parse_position_works() { + for (value, bytes) in get_positions() { + assert_eq!(value, Position::parse(&bytes).unwrap().1); + } + } + #[test] + fn serialize_position_works() { + for (value, bytes) in get_positions() { + assert_eq!(bytes, value.serialize()); + } + } +} diff --git a/crates/composition-protocol/src/packet/clientbound/login.rs b/crates/composition-protocol/src/packet/clientbound/login.rs deleted file mode 100644 index a386581..0000000 --- a/crates/composition-protocol/src/packet/clientbound/login.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::{util::*, Chat, Uuid}; - -#[derive(Clone, Debug, PartialEq)] -pub struct CL00Disconnect { - pub reason: Chat, -} -crate::packet::packet!( - CL00Disconnect, - 0x00, - crate::ClientState::Login, - false, - |data: &'data [u8]| -> ParseResult<'data, CL00Disconnect> { - let (data, reason) = parse_json(data)?; - Ok((data, CL00Disconnect { reason })) - }, - |packet: &CL00Disconnect| -> Vec { serialize_json(&packet.reason) } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct CL01EncryptionRequest { - pub server_id: String, - pub public_key: Vec, - pub verify_token: Vec, -} -crate::packet::packet!( - CL01EncryptionRequest, - 0x01, - crate::ClientState::Login, - false, - |data: &'data [u8]| -> ParseResult<'data, CL01EncryptionRequest> { - let (data, server_id) = parse_string(data)?; - let (data, public_key_len) = parse_varint(data)?; - let (data, public_key) = take_bytes(public_key_len as usize)(data)?; - let (data, verify_token_len) = parse_varint(data)?; - let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?; - - Ok(( - data, - CL01EncryptionRequest { - server_id, - public_key: public_key.to_vec(), - verify_token: verify_token.to_vec(), - }, - )) - }, - |packet: &CL01EncryptionRequest| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_string(&packet.server_id)); - output.extend_from_slice(&serialize_varint(packet.public_key.len() as i32)); - output.extend_from_slice(&packet.public_key); - output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32)); - output.extend_from_slice(&packet.verify_token); - output - } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct CL02LoginSuccess { - pub uuid: Uuid, - pub username: String, - pub properties: Vec, -} -#[derive(Clone, Debug, PartialEq)] -pub struct CL02LoginSuccessProperty { - pub name: String, - pub value: String, - pub signature: Option, -} -impl CL02LoginSuccessProperty { - pub fn parse(data: &[u8]) -> ParseResult<'_, Self> { - let (data, name) = parse_string(data)?; - let (data, value) = parse_string(data)?; - let (data, is_signed) = take_bytes(1usize)(data)?; - if is_signed == [0x01] { - let (data, signature) = parse_string(data)?; - Ok(( - data, - CL02LoginSuccessProperty { - name, - value, - signature: Some(signature), - }, - )) - } else { - Ok(( - data, - CL02LoginSuccessProperty { - name, - value, - signature: None, - }, - )) - } - } - pub fn serialize(&self) -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_string(&self.name)); - output.extend_from_slice(&serialize_string(&self.value)); - match &self.signature { - Some(signature) => { - output.push(0x01); - output.extend_from_slice(&serialize_string(signature)); - } - None => output.push(0x00), - } - output - } -} -crate::packet::packet!( - CL02LoginSuccess, - 0x02, - crate::ClientState::Login, - false, - |data: &'data [u8]| -> ParseResult<'data, CL02LoginSuccess> { - let (data, uuid) = parse_uuid(data)?; - let (data, username) = parse_string(data)?; - let (mut data, properties_len) = parse_varint(data)?; - let mut properties = Vec::with_capacity(properties_len as usize); - for _ in 0..properties_len { - let (d, property) = CL02LoginSuccessProperty::parse(data)?; - data = d; - properties.push(property); - } - - Ok(( - data, - CL02LoginSuccess { - uuid, - username, - properties, - }, - )) - }, - |packet: &CL02LoginSuccess| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_uuid(&packet.uuid)); - output.extend_from_slice(&serialize_string(&packet.username)); - output.extend_from_slice(&serialize_varint(packet.properties.len() as i32)); - for property in &packet.properties { - output.extend_from_slice(&property.serialize()); - } - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CL03SetCompression { - pub threshold: i32, -} -crate::packet::packet!( - CL03SetCompression, - 0x03, - crate::ClientState::Login, - false, - |data: &'data [u8]| -> ParseResult<'data, CL03SetCompression> { - let (data, threshold) = parse_varint(data)?; - Ok((data, CL03SetCompression { threshold })) - }, - |packet: &CL03SetCompression| -> Vec { serialize_varint(packet.threshold) } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct CL04LoginPluginRequest { - pub message_id: i32, - pub channel: String, - pub data: Vec, -} -crate::packet::packet!( - CL04LoginPluginRequest, - 0x04, - crate::ClientState::Login, - false, - |data: &'data [u8]| -> ParseResult<'data, CL04LoginPluginRequest> { - let (data, message_id) = parse_varint(data)?; - let (data, channel) = parse_string(data)?; - Ok(( - data, - CL04LoginPluginRequest { - message_id, - channel, - data: data.to_vec(), - }, - )) - }, - |packet: &CL04LoginPluginRequest| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.message_id)); - output.extend_from_slice(&serialize_string(&packet.channel)); - output.extend_from_slice(&packet.data); - output - } -); diff --git a/crates/composition-protocol/src/packet/clientbound/play.rs b/crates/composition-protocol/src/packet/clientbound/play.rs deleted file mode 100644 index 0421521..0000000 --- a/crates/composition-protocol/src/packet/clientbound/play.rs +++ /dev/null @@ -1,358 +0,0 @@ -use crate::{util::*, Chat, Difficulty, ProtocolError, Uuid}; -use byteorder::{BigEndian, ReadBytesExt}; - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CP00SpawnEntity { - pub entity_id: i32, - pub entity_uuid: Uuid, - pub kind: i32, - pub x: f64, - pub y: f64, - pub z: f64, - pub pitch: u8, - pub yaw: u8, - pub head_yaw: u8, - pub data: i32, - pub velocity_x: i16, - pub velocity_y: i16, - pub velocity_z: i16, -} -crate::packet::packet!( - CP00SpawnEntity, - 0x00, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP00SpawnEntity> { - let (data, entity_id) = parse_varint(data)?; - let (data, entity_uuid) = parse_uuid(data)?; - let (data, kind) = parse_varint(data)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let x = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let y = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let z = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, t) = take_bytes(3usize)(data)?; - let (data, d) = parse_varint(data)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let velocity_x = bytes - .read_i16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let velocity_y = bytes - .read_i16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let velocity_z = bytes - .read_i16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - - Ok(( - data, - CP00SpawnEntity { - entity_id, - entity_uuid, - kind, - x, - y, - z, - pitch: t[0], - yaw: t[1], - head_yaw: t[2], - data: d, - velocity_x, - velocity_y, - velocity_z, - }, - )) - }, - |packet: &CP00SpawnEntity| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.entity_id)); - output.extend_from_slice(&serialize_uuid(&packet.entity_uuid)); - output.extend_from_slice(&serialize_varint(packet.kind)); - output.extend_from_slice(&packet.x.to_be_bytes()); - output.extend_from_slice(&packet.y.to_be_bytes()); - output.extend_from_slice(&packet.z.to_be_bytes()); - output.push(packet.pitch); - output.push(packet.yaw); - output.push(packet.head_yaw); - output.extend_from_slice(&serialize_varint(packet.data)); - output.extend_from_slice(&packet.velocity_x.to_be_bytes()); - output.extend_from_slice(&packet.velocity_y.to_be_bytes()); - output.extend_from_slice(&packet.velocity_z.to_be_bytes()); - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CP0BChangeDifficulty { - pub difficulty: Difficulty, - pub is_locked: bool, -} -crate::packet::packet!( - CP0BChangeDifficulty, - 0x0b, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP0BChangeDifficulty> { - let (data, difficulty) = take_bytes(1)(data)?; - let difficulty: Difficulty = difficulty[0] - .try_into() - .expect("TODO: handle incorrect difficulty"); - let (data, is_locked) = take_bytes(1)(data)?; - let is_locked = is_locked[0] > 0; - Ok(( - data, - CP0BChangeDifficulty { - difficulty, - is_locked, - }, - )) - }, - |packet: &CP0BChangeDifficulty| -> Vec { - let mut output = vec![]; - output.push(packet.difficulty as u8); - output.push(if packet.is_locked { 0x01 } else { 0x00 }); - output - } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct CP17Disconnect { - pub reason: Chat, -} -crate::packet::packet!( - CP17Disconnect, - 0x17, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP17Disconnect> { - let (data, reason) = parse_json(data)?; - Ok((data, CP17Disconnect { reason })) - }, - |packet: &CP17Disconnect| -> Vec { serialize_json(&packet.reason) } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CP1FKeepAlive { - pub payload: i64, -} -crate::packet::packet!( - CP1FKeepAlive, - 0x1f, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP1FKeepAlive> { - let (data, mut bytes) = take_bytes(8)(data)?; - let payload = bytes - .read_i64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - Ok((data, CP1FKeepAlive { payload })) - }, - |packet: &CP1FKeepAlive| -> Vec { packet.payload.to_be_bytes().to_vec() } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CP21WorldEvent { - pub event: i32, - pub location: Position, - pub data: i32, - pub disable_relative_volume: bool, -} -crate::packet::packet!( - CP21WorldEvent, - 0x21, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP21WorldEvent> { - let (data, mut bytes) = take_bytes(4)(data)?; - let event = bytes - .read_i32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, location) = Position::parse(data)?; - let (data, mut bytes) = take_bytes(4)(data)?; - let d = bytes - .read_i32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, disable_relative_volume) = take_bytes(1usize)(data)?; - let disable_relative_volume = disable_relative_volume == [0x01]; - Ok(( - data, - CP21WorldEvent { - event, - location, - data: d, - disable_relative_volume, - }, - )) - }, - |packet: &CP21WorldEvent| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&packet.event.to_be_bytes()); - output.extend_from_slice(&packet.location.serialize()); - output.extend_from_slice(&packet.data.to_be_bytes()); - output.push(if packet.disable_relative_volume { - 0x01 - } else { - 0x00 - }); - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CP50SetEntityVelocity { - pub entity_id: i32, - pub velocity_x: i16, - pub velocity_y: i16, - pub velocity_z: i16, -} -crate::packet::packet!( - CP50SetEntityVelocity, - 0x50, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP50SetEntityVelocity> { - let (data, entity_id) = parse_varint(data)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let velocity_x = bytes - .read_i16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let velocity_y = bytes - .read_i16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let velocity_z = bytes - .read_i16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - Ok(( - data, - CP50SetEntityVelocity { - entity_id, - velocity_x, - velocity_y, - velocity_z, - }, - )) - }, - |packet: &CP50SetEntityVelocity| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.entity_id)); - output.extend_from_slice(&packet.velocity_x.to_be_bytes()); - output.extend_from_slice(&packet.velocity_y.to_be_bytes()); - output.extend_from_slice(&packet.velocity_z.to_be_bytes()); - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CP52SetExperience { - pub experience_bar: f32, - pub total_experience: i32, - pub level: i32, -} -crate::packet::packet!( - CP52SetExperience, - 0x52, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP52SetExperience> { - let (data, mut bytes) = take_bytes(4)(data)?; - let experience_bar = bytes - .read_f32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, total_experience) = parse_varint(data)?; - let (data, level) = parse_varint(data)?; - Ok(( - data, - CP52SetExperience { - experience_bar, - total_experience, - level, - }, - )) - }, - |packet: &CP52SetExperience| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&packet.experience_bar.to_be_bytes()); - output.extend_from_slice(&serialize_varint(packet.total_experience)); - output.extend_from_slice(&serialize_varint(packet.level)); - output - } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct CP68EntityEffect { - pub entity_id: i32, - pub effect_id: i32, - pub amplifier: i8, - pub duration: i32, - pub is_ambient: bool, - pub show_particles: bool, - pub show_icon: bool, - pub has_factor_data: bool, - // TODO: pub factor_codec: NBT -} -crate::packet::packet!( - CP68EntityEffect, - 0x68, - crate::ClientState::Play, - false, - |data: &'data [u8]| -> ParseResult<'data, CP68EntityEffect> { - let (data, entity_id) = parse_varint(data)?; - let (data, effect_id) = parse_varint(data)?; - let (data, amplifier) = take_bytes(1)(data)?; - let amplifier = amplifier[0] as i8; - let (data, duration) = parse_varint(data)?; - let (data, flags) = take_bytes(1)(data)?; - let flags = flags[0] as i8; - let is_ambient = flags & 0x01 > 0; - let show_particles = flags & 0x02 > 0; - let show_icon = flags & 0x04 > 0; - let (data, has_factor_data) = take_bytes(1)(data)?; - let has_factor_data = has_factor_data[0] > 0; - // TODO: factor_codec - - Ok(( - data, - CP68EntityEffect { - entity_id, - effect_id, - amplifier, - duration, - is_ambient, - show_particles, - show_icon, - has_factor_data, - }, - )) - }, - |packet: &CP68EntityEffect| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.entity_id)); - output.extend_from_slice(&serialize_varint(packet.effect_id)); - output.push(packet.amplifier as u8); - output.extend_from_slice(&serialize_varint(packet.duration)); - let mut flags = 0x00i8; - if packet.is_ambient { - flags |= 0x01; - } - if packet.show_particles { - flags |= 0x02; - } - if packet.show_icon { - flags |= 0x04; - } - output.push(flags as u8); - // TODO: factor_codec - output - } -); diff --git a/crates/composition-protocol/src/packet/clientbound/status.rs b/crates/composition-protocol/src/packet/clientbound/status.rs deleted file mode 100644 index 64da8ea..0000000 --- a/crates/composition-protocol/src/packet/clientbound/status.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::{util::*, Json, ProtocolError}; -use byteorder::{BigEndian, ReadBytesExt}; - -#[derive(Clone, Debug, PartialEq)] -pub struct CS00StatusResponse { - pub response: Json, -} -crate::packet::packet!( - CS00StatusResponse, - 0x00, - crate::ClientState::Status, - false, - |data: &'data [u8]| -> ParseResult<'data, CS00StatusResponse> { - let (data, response) = parse_json(data)?; - Ok((data, CS00StatusResponse { response })) - }, - |packet: &CS00StatusResponse| -> Vec { serialize_json(&packet.response) } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct CS01PingResponse { - pub payload: i64, -} -crate::packet::packet!( - CS01PingResponse, - 0x01, - crate::ClientState::Status, - false, - |data: &'data [u8]| -> ParseResult<'data, CS01PingResponse> { - let (data, mut bytes) = take_bytes(8)(data)?; - let payload = bytes - .read_i64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - Ok((data, CS01PingResponse { payload })) - }, - |packet: &CS01PingResponse| -> Vec { packet.payload.to_be_bytes().to_vec() } -); diff --git a/crates/composition-protocol/src/packet/serverbound/handshake.rs b/crates/composition-protocol/src/packet/serverbound/handshake.rs deleted file mode 100644 index 43ce7ca..0000000 --- a/crates/composition-protocol/src/packet/serverbound/handshake.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::{util::*, ClientState, ProtocolError}; -use byteorder::{BigEndian, ReadBytesExt}; - -#[derive(Clone, Debug, PartialEq)] -pub struct SH00Handshake { - pub protocol_version: i32, - pub server_address: String, - pub server_port: u16, - pub next_state: ClientState, -} -crate::packet::packet!( - SH00Handshake, - 0x00, - ClientState::Handshake, - true, - |data: &'data [u8]| -> ParseResult<'data, SH00Handshake> { - let (data, protocol_version) = parse_varint(data)?; - let (data, server_address) = parse_string(data)?; - let (data, mut bytes) = take_bytes(2)(data)?; - let server_port = bytes - .read_u16::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, next_state) = parse_varint(data)?; - - Ok(( - data, - SH00Handshake { - protocol_version, - server_address, - server_port, - next_state: match next_state { - 1 => ClientState::Status, - 2 => ClientState::Login, - _ => todo!("Invalid next state"), - }, - }, - )) - }, - |packet: &SH00Handshake| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&packet.protocol_version.to_be_bytes()); - output.extend_from_slice(&serialize_string(&packet.server_address)); - output.extend_from_slice(&packet.server_port.to_be_bytes()); - output.extend_from_slice(&serialize_varint(match packet.next_state { - ClientState::Status => 0x01, - ClientState::Login => 0x02, - _ => panic!("invalid SH00Handshake next_state"), - })); - output - } -); diff --git a/crates/composition-protocol/src/packet/serverbound/login.rs b/crates/composition-protocol/src/packet/serverbound/login.rs deleted file mode 100644 index 30d45b6..0000000 --- a/crates/composition-protocol/src/packet/serverbound/login.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::{util::*, Uuid}; - -#[derive(Clone, Debug, PartialEq)] -pub struct SL00LoginStart { - pub name: String, - pub uuid: Option, -} -crate::packet::packet!( - SL00LoginStart, - 0x00, - crate::ClientState::Login, - true, - |data: &'data [u8]| -> ParseResult<'data, SL00LoginStart> { - let (data, name) = parse_string(data)?; - let (data, has_uuid) = take_bytes(1usize)(data)?; - if has_uuid == [0x01] { - let (data, uuid) = parse_uuid(data)?; - Ok(( - data, - SL00LoginStart { - name, - uuid: Some(uuid), - }, - )) - } else { - Ok((data, SL00LoginStart { name, uuid: None })) - } - }, - |packet: &SL00LoginStart| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_string(&packet.name)); - match packet.uuid { - Some(uuid) => { - output.push(0x01); - output.extend_from_slice(&serialize_uuid(&uuid)); - } - None => output.push(0x00), - } - output - } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct SL01EncryptionResponse { - pub shared_secret: Vec, - pub verify_token: Vec, -} -crate::packet::packet!( - SL01EncryptionResponse, - 0x01, - crate::ClientState::Login, - true, - |data: &'data [u8]| -> ParseResult<'data, SL01EncryptionResponse> { - let (data, shared_secret_len) = parse_varint(data)?; - let (data, shared_secret) = take_bytes(shared_secret_len as usize)(data)?; - let (data, verify_token_len) = parse_varint(data)?; - let (data, verify_token) = take_bytes(verify_token_len as usize)(data)?; - - Ok(( - data, - SL01EncryptionResponse { - shared_secret: shared_secret.to_vec(), - verify_token: verify_token.to_vec(), - }, - )) - }, - |packet: &SL01EncryptionResponse| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.shared_secret.len() as i32)); - output.extend_from_slice(&packet.shared_secret); - output.extend_from_slice(&serialize_varint(packet.verify_token.len() as i32)); - output.extend_from_slice(&packet.verify_token); - output - } -); - -#[derive(Clone, Debug, PartialEq)] -pub struct SL02LoginPluginResponse { - pub message_id: i32, - pub successful: bool, - pub data: Vec, -} -crate::packet::packet!( - SL02LoginPluginResponse, - 0x02, - crate::ClientState::Login, - true, - |data: &'data [u8]| -> ParseResult<'data, SL02LoginPluginResponse> { - let (data, message_id) = parse_varint(data)?; - let (data, successful) = take_bytes(1usize)(data)?; - let successful = successful == [0x01]; - Ok(( - data, - SL02LoginPluginResponse { - message_id, - successful, - data: match successful { - true => data.to_vec(), - false => vec![], - }, - }, - )) - }, - |packet: &SL02LoginPluginResponse| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.message_id)); - if packet.successful { - output.push(0x01); - output.extend_from_slice(&packet.data); - } else { - output.push(0x00); - } - output - } -); diff --git a/crates/composition-protocol/src/packet/serverbound/play.rs b/crates/composition-protocol/src/packet/serverbound/play.rs deleted file mode 100644 index 8720ffd..0000000 --- a/crates/composition-protocol/src/packet/serverbound/play.rs +++ /dev/null @@ -1,190 +0,0 @@ -use crate::{util::*, ProtocolError}; -use byteorder::{BigEndian, ReadBytesExt}; - -#[derive(Clone, Debug, PartialEq)] -pub struct SP08CommandSuggestionsRequest { - pub transaction_id: i32, - pub text: String, -} -crate::packet::packet!( - SP08CommandSuggestionsRequest, - 0x08, - crate::ClientState::Play, - true, - |data: &'data [u8]| -> ParseResult<'data, SP08CommandSuggestionsRequest> { - let (data, transaction_id) = parse_varint(data)?; - let (data, text) = parse_string(data)?; - Ok(( - data, - SP08CommandSuggestionsRequest { - transaction_id, - text, - }, - )) - }, - |packet: &SP08CommandSuggestionsRequest| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(packet.transaction_id)); - output.extend_from_slice(&serialize_string(&packet.text)); - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SP11KeepAlive { - pub payload: i64, -} -crate::packet::packet!( - SP11KeepAlive, - 0x11, - crate::ClientState::Play, - true, - |data: &'data [u8]| -> ParseResult<'data, SP11KeepAlive> { - let (data, mut bytes) = take_bytes(8)(data)?; - let payload = bytes - .read_i64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - Ok((data, SP11KeepAlive { payload })) - }, - |packet: &SP11KeepAlive| -> Vec { packet.payload.to_be_bytes().to_vec() } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SP13SetPlayerPosition { - pub x: f64, - pub y: f64, - pub z: f64, - pub on_ground: bool, -} -crate::packet::packet!( - SP13SetPlayerPosition, - 0x13, - crate::ClientState::Play, - true, - |data: &'data [u8]| -> ParseResult<'data, SP13SetPlayerPosition> { - let (data, mut bytes) = take_bytes(8)(data)?; - let x = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let y = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let z = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, on_ground) = take_bytes(1usize)(data)?; - let on_ground = on_ground == [0x01]; - Ok((data, SP13SetPlayerPosition { x, y, z, on_ground })) - }, - |packet: &SP13SetPlayerPosition| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&packet.x.to_be_bytes()); - output.extend_from_slice(&packet.y.to_be_bytes()); - output.extend_from_slice(&packet.z.to_be_bytes()); - output.push(if packet.on_ground { 0x01 } else { 0x00 }); - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SP14SetPlayerPositionAndRotation { - pub x: f64, - pub y: f64, - pub z: f64, - pub yaw: f32, - pub pitch: f32, - pub on_ground: bool, -} -crate::packet::packet!( - SP14SetPlayerPositionAndRotation, - 0x14, - crate::ClientState::Play, - true, - |data: &'data [u8]| -> ParseResult<'data, SP14SetPlayerPositionAndRotation> { - let (data, mut bytes) = take_bytes(8)(data)?; - let x = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let y = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(8)(data)?; - let z = bytes - .read_f64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(4)(data)?; - let yaw = bytes - .read_f32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(4)(data)?; - let pitch = bytes - .read_f32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, on_ground) = take_bytes(1usize)(data)?; - let on_ground = on_ground == [0x01]; - Ok(( - data, - SP14SetPlayerPositionAndRotation { - x, - y, - z, - yaw, - pitch, - on_ground, - }, - )) - }, - |packet: &SP14SetPlayerPositionAndRotation| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&packet.x.to_be_bytes()); - output.extend_from_slice(&packet.y.to_be_bytes()); - output.extend_from_slice(&packet.z.to_be_bytes()); - output.extend_from_slice(&packet.yaw.to_be_bytes()); - output.extend_from_slice(&packet.pitch.to_be_bytes()); - output.push(if packet.on_ground { 0x01 } else { 0x00 }); - output - } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SP15SetPlayerRotation { - pub yaw: f32, - pub pitch: f32, - pub on_ground: bool, -} -crate::packet::packet!( - SP15SetPlayerRotation, - 0x15, - crate::ClientState::Play, - true, - |data: &'data [u8]| -> ParseResult<'data, SP15SetPlayerRotation> { - let (data, mut bytes) = take_bytes(4)(data)?; - let yaw = bytes - .read_f32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, mut bytes) = take_bytes(4)(data)?; - let pitch = bytes - .read_f32::() - .map_err(|_| ProtocolError::NotEnoughData)?; - let (data, on_ground) = take_bytes(1usize)(data)?; - let on_ground = on_ground == [0x01]; - Ok(( - data, - SP15SetPlayerRotation { - yaw, - pitch, - on_ground, - }, - )) - }, - |packet: &SP15SetPlayerRotation| -> Vec { - let mut output = vec![]; - output.extend_from_slice(&packet.yaw.to_be_bytes()); - output.extend_from_slice(&packet.pitch.to_be_bytes()); - output.push(if packet.on_ground { 0x01 } else { 0x00 }); - output - } -); diff --git a/crates/composition-protocol/src/packet/serverbound/status.rs b/crates/composition-protocol/src/packet/serverbound/status.rs deleted file mode 100644 index 952c2fe..0000000 --- a/crates/composition-protocol/src/packet/serverbound/status.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::{util::*, ProtocolError}; -use byteorder::{BigEndian, ReadBytesExt}; - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SS00StatusRequest; -crate::packet::packet!( - SS00StatusRequest, - 0x00, - crate::ClientState::Status, - true, - |data: &'data [u8]| -> ParseResult<'data, SS00StatusRequest> { Ok((data, SS00StatusRequest)) }, - |_packet: &SS00StatusRequest| -> Vec { vec![] } -); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct SS01PingRequest { - pub payload: i64, -} -crate::packet::packet!( - SS01PingRequest, - 0x01, - crate::ClientState::Status, - true, - |data: &'data [u8]| -> ParseResult<'data, SS01PingRequest> { - let (data, mut bytes) = take_bytes(8)(data)?; - let payload = bytes - .read_i64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - Ok((data, SS01PingRequest { payload })) - }, - |packet: &SS01PingRequest| -> Vec { packet.payload.to_be_bytes().to_vec() } -); diff --git a/crates/composition-protocol/src/packets/clientbound/login.rs b/crates/composition-protocol/src/packets/clientbound/login.rs new file mode 100644 index 0000000..7b79434 --- /dev/null +++ b/crates/composition-protocol/src/packets/clientbound/login.rs @@ -0,0 +1,164 @@ +use crate::mctypes::{Chat, Json, Uuid, VarInt}; +use composition_parsing::Parsable; + +#[derive(Clone, Debug, PartialEq)] +pub struct CL00Disconnect { + pub reason: Chat, +} +crate::packets::packet!( + CL00Disconnect, + 0x00, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CL00Disconnect> { + let (data, reason) = Json::parse(data)?; + Ok((data, CL00Disconnect { reason })) + }, + |packet: &CL00Disconnect| -> Vec { packet.reason.serialize() } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct CL01EncryptionRequest { + pub server_id: String, + pub public_key: Vec, + pub verify_token: Vec, +} +crate::packets::packet!( + CL01EncryptionRequest, + 0x01, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CL01EncryptionRequest> { + let (data, server_id) = String::parse(data)?; + let (data, public_key) = u8::parse_vec(data)?; + let (data, verify_token) = u8::parse_vec(data)?; + + Ok(( + data, + CL01EncryptionRequest { + server_id, + public_key, + verify_token, + }, + )) + }, + |packet: &CL01EncryptionRequest| -> Vec { + let mut output = vec![]; + output.extend(packet.server_id.serialize()); + output.extend(packet.public_key.serialize()); + output.extend(packet.verify_token.serialize()); + output + } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct CL02LoginSuccess { + pub uuid: Uuid, + pub username: String, + pub properties: Vec, +} +#[derive(Clone, Debug, PartialEq)] +pub struct CL02LoginSuccessProperty { + pub name: String, + pub value: String, + pub signature: Option, +} +impl Parsable for CL02LoginSuccessProperty { + #[tracing::instrument] + fn parse(data: &[u8]) -> composition_parsing::ParseResult<'_, Self> { + let (data, name) = String::parse(data)?; + let (data, value) = String::parse(data)?; + let (data, signature) = String::parse_optional(data)?; + Ok(( + data, + CL02LoginSuccessProperty { + name, + value, + signature, + }, + )) + } + #[tracing::instrument] + fn serialize(&self) -> Vec { + let mut output = vec![]; + output.extend(self.name.serialize()); + output.extend(self.value.serialize()); + output.extend(self.signature.serialize()); + output + } +} +crate::packets::packet!( + CL02LoginSuccess, + 0x02, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CL02LoginSuccess> { + let (data, uuid) = Uuid::parse(data)?; + let (data, username) = String::parse(data)?; + let (data, properties) = CL02LoginSuccessProperty::parse_vec(data)?; + + Ok(( + data, + CL02LoginSuccess { + uuid, + username, + properties, + }, + )) + }, + |packet: &CL02LoginSuccess| -> Vec { + let mut output = vec![]; + output.extend(packet.uuid.serialize()); + output.extend(packet.username.serialize()); + output.extend(packet.properties.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CL03SetCompression { + pub threshold: VarInt, +} +crate::packets::packet!( + CL03SetCompression, + 0x03, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CL03SetCompression> { + let (data, threshold) = VarInt::parse(data)?; + Ok((data, CL03SetCompression { threshold })) + }, + |packet: &CL03SetCompression| -> Vec { packet.threshold.serialize() } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct CL04LoginPluginRequest { + pub message_id: VarInt, + pub channel: String, + pub data: Vec, +} +crate::packets::packet!( + CL04LoginPluginRequest, + 0x04, + crate::ClientState::Login, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CL04LoginPluginRequest> { + let (data, message_id) = VarInt::parse(data)?; + let (data, channel) = String::parse(data)?; + Ok(( + data, + CL04LoginPluginRequest { + message_id, + channel, + data: data.to_vec(), + }, + )) + }, + |packet: &CL04LoginPluginRequest| -> Vec { + let mut output = vec![]; + output.extend(packet.message_id.serialize()); + output.extend(packet.channel.serialize()); + output.extend(&packet.data); + output + } +); diff --git a/crates/composition-protocol/src/packet/clientbound/mod.rs b/crates/composition-protocol/src/packets/clientbound/mod.rs similarity index 100% rename from crates/composition-protocol/src/packet/clientbound/mod.rs rename to crates/composition-protocol/src/packets/clientbound/mod.rs diff --git a/crates/composition-protocol/src/packets/clientbound/play.rs b/crates/composition-protocol/src/packets/clientbound/play.rs new file mode 100644 index 0000000..8ba993b --- /dev/null +++ b/crates/composition-protocol/src/packets/clientbound/play.rs @@ -0,0 +1,283 @@ +use crate::{ + entities::{EntityPosition, EntityRotation, EntityVelocity}, + mctypes::{Chat, Difficulty, Position, Uuid, VarInt}, +}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CP00SpawnEntity { + pub id: VarInt, + pub uuid: Uuid, + pub kind: VarInt, + pub position: EntityPosition, + pub rotation: EntityRotation, + pub head_yaw: u8, + pub data: VarInt, + pub velocity: EntityVelocity, +} +crate::packets::packet!( + CP00SpawnEntity, + 0x00, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP00SpawnEntity> { + let (data, id) = VarInt::parse(data)?; + let (data, uuid) = Uuid::parse(data)?; + let (data, kind) = VarInt::parse(data)?; + let (data, position) = EntityPosition::parse(data)?; + let (data, rotation) = EntityRotation::parse(data)?; + let (data, head_yaw) = u8::parse(data)?; + let (data, d) = VarInt::parse(data)?; + let (data, velocity) = EntityVelocity::parse(data)?; + + Ok(( + data, + CP00SpawnEntity { + id, + uuid, + kind, + position, + rotation, + head_yaw, + data: d, + velocity, + }, + )) + }, + |packet: &CP00SpawnEntity| -> Vec { + let mut output = vec![]; + output.extend(packet.id.serialize()); + output.extend(packet.uuid.serialize()); + output.extend(packet.kind.serialize()); + output.extend(packet.position.serialize()); + output.extend(packet.rotation.serialize()); + output.extend(packet.head_yaw.serialize()); + output.extend(packet.data.serialize()); + output.extend(packet.velocity.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CP0BChangeDifficulty { + pub difficulty: Difficulty, + pub is_locked: bool, +} +crate::packets::packet!( + CP0BChangeDifficulty, + 0x0b, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP0BChangeDifficulty> { + let (data, difficulty) = Difficulty::parse(data)?; + let (data, is_locked) = bool::parse(data)?; + Ok(( + data, + CP0BChangeDifficulty { + difficulty, + is_locked, + }, + )) + }, + |packet: &CP0BChangeDifficulty| -> Vec { + let mut output = vec![]; + output.extend(packet.difficulty.serialize()); + output.extend(packet.is_locked.serialize()); + output + } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct CP17Disconnect { + pub reason: Chat, +} +crate::packets::packet!( + CP17Disconnect, + 0x17, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP17Disconnect> { + let (data, reason) = Chat::parse(data)?; + Ok((data, CP17Disconnect { reason })) + }, + |packet: &CP17Disconnect| -> Vec { packet.reason.serialize() } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CP1FKeepAlive { + pub payload: i64, +} +crate::packets::packet!( + CP1FKeepAlive, + 0x1f, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP1FKeepAlive> { + let (data, payload) = i64::parse(data)?; + Ok((data, CP1FKeepAlive { payload })) + }, + |packet: &CP1FKeepAlive| -> Vec { packet.payload.serialize() } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CP21WorldEvent { + pub event: i32, + pub location: Position, + pub data: i32, + pub disable_relative_volume: bool, +} +crate::packets::packet!( + CP21WorldEvent, + 0x21, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP21WorldEvent> { + let (data, event) = i32::parse(data)?; + let (data, location) = Position::parse(data)?; + let (data, d) = i32::parse(data)?; + let (data, disable_relative_volume) = bool::parse(data)?; + Ok(( + data, + CP21WorldEvent { + event, + location, + data: d, + disable_relative_volume, + }, + )) + }, + |packet: &CP21WorldEvent| -> Vec { + let mut output = vec![]; + output.extend(packet.event.serialize()); + output.extend(packet.location.serialize()); + output.extend(packet.data.serialize()); + output.extend(packet.disable_relative_volume.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CP50SetEntityVelocity { + pub entity_id: VarInt, + pub entity_velocity: EntityVelocity, +} +crate::packets::packet!( + CP50SetEntityVelocity, + 0x50, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP50SetEntityVelocity> { + let (data, entity_id) = VarInt::parse(data)?; + let (data, entity_velocity) = EntityVelocity::parse(data)?; + Ok(( + data, + CP50SetEntityVelocity { + entity_id, + entity_velocity, + }, + )) + }, + |packet: &CP50SetEntityVelocity| -> Vec { + let mut output = vec![]; + output.extend(packet.entity_id.serialize()); + output.extend(packet.entity_velocity.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CP52SetExperience { + pub experience_bar: f32, + pub total_experience: VarInt, + pub level: VarInt, +} +crate::packets::packet!( + CP52SetExperience, + 0x52, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP52SetExperience> { + let (data, experience_bar) = f32::parse(data)?; + let (data, total_experience) = VarInt::parse(data)?; + let (data, level) = VarInt::parse(data)?; + Ok(( + data, + CP52SetExperience { + experience_bar, + total_experience, + level, + }, + )) + }, + |packet: &CP52SetExperience| -> Vec { + let mut output = vec![]; + output.extend(packet.experience_bar.serialize()); + output.extend(packet.total_experience.serialize()); + output.extend(packet.level.serialize()); + output + } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct CP68EntityEffect { + pub entity_id: VarInt, + pub effect_id: VarInt, + pub amplifier: i8, + pub duration: VarInt, + pub is_ambient: bool, + pub show_particles: bool, + pub show_icon: bool, + pub has_factor_data: bool, + // TODO: pub factor_codec: NBT +} +crate::packets::packet!( + CP68EntityEffect, + 0x68, + crate::ClientState::Play, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CP68EntityEffect> { + let (data, entity_id) = VarInt::parse(data)?; + let (data, effect_id) = VarInt::parse(data)?; + let (data, amplifier) = i8::parse(data)?; + let (data, duration) = VarInt::parse(data)?; + let (data, flags) = u8::parse(data)?; + let is_ambient = flags & 0x01 > 0; + let show_particles = flags & 0x02 > 0; + let show_icon = flags & 0x04 > 0; + let (data, has_factor_data) = bool::parse(data)?; + // TODO: factor_codec + + Ok(( + data, + CP68EntityEffect { + entity_id, + effect_id, + amplifier, + duration, + is_ambient, + show_particles, + show_icon, + has_factor_data, + }, + )) + }, + |packet: &CP68EntityEffect| -> Vec { + let mut output = vec![]; + output.extend(packet.entity_id.serialize()); + output.extend(packet.effect_id.serialize()); + output.extend(packet.amplifier.serialize()); + output.extend(packet.duration.serialize()); + let mut flags = 0x00u8; + if packet.is_ambient { + flags |= 0x01; + } + if packet.show_particles { + flags |= 0x02; + } + if packet.show_icon { + flags |= 0x04; + } + output.extend(flags.serialize()); + // TODO: factor_codec + output + } +); diff --git a/crates/composition-protocol/src/packets/clientbound/status.rs b/crates/composition-protocol/src/packets/clientbound/status.rs new file mode 100644 index 0000000..beba263 --- /dev/null +++ b/crates/composition-protocol/src/packets/clientbound/status.rs @@ -0,0 +1,33 @@ +use crate::mctypes::Json; + +#[derive(Clone, Debug, PartialEq)] +pub struct CS00StatusResponse { + pub response: Json, +} +crate::packets::packet!( + CS00StatusResponse, + 0x00, + crate::ClientState::Status, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CS00StatusResponse> { + let (data, response) = Json::parse(data)?; + Ok((data, CS00StatusResponse { response })) + }, + |packet: &CS00StatusResponse| -> Vec { packet.response.serialize() } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct CS01PingResponse { + pub payload: i64, +} +crate::packets::packet!( + CS01PingResponse, + 0x01, + crate::ClientState::Status, + false, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, CS01PingResponse> { + let (data, payload) = i64::parse(data)?; + Ok((data, CS01PingResponse { payload })) + }, + |packet: &CS01PingResponse| -> Vec { packet.payload.serialize() } +); diff --git a/crates/composition-protocol/src/packet/mod.rs b/crates/composition-protocol/src/packets/mod.rs similarity index 61% rename from crates/composition-protocol/src/packet/mod.rs rename to crates/composition-protocol/src/packets/mod.rs index f11e293..392e83f 100644 --- a/crates/composition-protocol/src/packet/mod.rs +++ b/crates/composition-protocol/src/packets/mod.rs @@ -1,18 +1,20 @@ pub mod clientbound; pub mod serverbound; -pub type PacketId = i32; +use crate::mctypes::VarInt; -pub trait Packet: std::fmt::Debug + Clone + TryFrom + Into { - const ID: PacketId; +pub type PacketId = crate::mctypes::VarInt; + +pub trait Packet: + std::fmt::Debug + + Clone + + TryFrom + + Into + + composition_parsing::Parsable +{ + const ID: i32; const CLIENT_STATE: crate::ClientState; const IS_SERVERBOUND: bool; - - // The slice given should only be the exact amount of data in the body. - fn parse_body(data: &[u8]) -> crate::util::ParseResult<'_, Self> - where - Self: Sized; - fn serialize_body(&self) -> Vec; } macro_rules! generic_packet { @@ -29,21 +31,18 @@ macro_rules! generic_packet { client_state: crate::ClientState, is_serverbound: bool, data: &'data [u8] - ) -> crate::util::ParseResult<'data, Self> { + ) -> composition_parsing::ParseResult<'data, Self> { + use composition_parsing::Parsable; tracing::trace!( "GenericPacket::parse_uncompressed: {:?} {} {:?}", client_state, is_serverbound, data ); - tracing::debug!("data before: {:?}", data); - let (data, packet_length) = crate::util::parse_varint(data)?; - tracing::debug!("data after packet length ({}): {:?}", packet_length, data); - let (data, packet_data) = crate::util::take_bytes(packet_length as usize)(data)?; - tracing::debug!("data after packet data ({:?}): {:?}", packet_data, data); + let (data, packet_length) = crate::mctypes::VarInt::parse(data)?; + let (data, packet_data) = composition_parsing::take_bytes(*packet_length as usize)(data)?; - let (packet_data, packet_id) = crate::util::parse_varint(packet_data)?; - tracing::debug!("packet_data after packet_id ({}): {:?}", packet_id, packet_data); + let (packet_data, packet_id) = PacketId::parse(packet_data)?; let (_packet_data, packet_body) = Self::parse_body(client_state, packet_id, is_serverbound, packet_data)?; @@ -57,30 +56,32 @@ macro_rules! generic_packet { #[tracing::instrument] pub fn parse_body<'data>( client_state: crate::ClientState, - packet_id: crate::packet::PacketId, + packet_id: crate::packets::PacketId, is_serverbound: bool, data: &'data [u8], - ) -> crate::util::ParseResult<'data, Self> { + ) -> composition_parsing::ParseResult<'data, Self> { + use composition_parsing::Parsable; tracing::trace!( "GenericPacket::parse_body: {:?} {} {}", client_state, packet_id, is_serverbound ); - match (client_state, packet_id, is_serverbound) { + match (client_state, *packet_id, is_serverbound) { $( - ($packet_type::CLIENT_STATE, $packet_type::ID, $packet_type::IS_SERVERBOUND) => $packet_type::parse_body(data).map(|(data, packet)| (data, Into::::into(packet))), + ($packet_type::CLIENT_STATE, $packet_type::ID, $packet_type::IS_SERVERBOUND) => $packet_type::parse(data).map(|(data, packet)| (data, Into::::into(packet))), )* _ => Ok((data, Self::UnimplementedPacket(UnimplementedPacket(packet_id)))), } } #[tracing::instrument] - pub fn serialize(&self) -> (crate::packet::PacketId, Vec) { + pub fn serialize(&self) -> (crate::packets::PacketId, Vec) { + use composition_parsing::Parsable; tracing::trace!("GenericPacket::serialize: {:?}", self); match self { $( - Self::$packet_type(packet) => ($packet_type::ID, packet.serialize_body()), + Self::$packet_type(packet) => (PacketId::from($packet_type::ID), packet.serialize()), )* } } @@ -89,14 +90,14 @@ macro_rules! generic_packet { } #[derive(Copy, Clone, Debug, PartialEq)] -pub struct UnimplementedPacket(i32); +pub struct UnimplementedPacket(VarInt); packet!( UnimplementedPacket, 0x00, crate::ClientState::Disconnected, false, - |data: &'data [u8]| -> crate::util::ParseResult<'data, UnimplementedPacket> { - Ok((data, UnimplementedPacket(0i32))) + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, UnimplementedPacket> { + Ok((data, UnimplementedPacket(0i32.into()))) }, |_packet: &UnimplementedPacket| -> Vec { vec![] } ); @@ -139,29 +140,32 @@ generic_packet!( macro_rules! packet { ($packet_type: ident, $id: literal, $client_state: expr, $serverbound: literal, $parse_body: expr, $serialize_body: expr) => { - impl crate::packet::Packet for $packet_type { - const ID: crate::packet::PacketId = $id; + impl crate::packets::Packet for $packet_type { + const ID: i32 = $id; const CLIENT_STATE: crate::ClientState = $client_state; const IS_SERVERBOUND: bool = $serverbound; - - fn parse_body<'data>(data: &'data [u8]) -> crate::util::ParseResult<'_, $packet_type> { + } + impl composition_parsing::Parsable for $packet_type { + #[tracing::instrument] + fn parse<'data>(data: &'data [u8]) -> composition_parsing::ParseResult<'_, Self> { $parse_body(data) } - fn serialize_body(&self) -> Vec { + #[tracing::instrument] + fn serialize(&self) -> Vec { $serialize_body(self) } } - impl From<$packet_type> for crate::packet::GenericPacket { + impl From<$packet_type> for crate::packets::GenericPacket { fn from(value: $packet_type) -> Self { - crate::packet::GenericPacket::$packet_type(value) + crate::packets::GenericPacket::$packet_type(value) } } - impl TryFrom for $packet_type { + impl TryFrom for $packet_type { type Error = (); - fn try_from(value: crate::packet::GenericPacket) -> Result { + fn try_from(value: crate::packets::GenericPacket) -> Result { match value { - crate::packet::GenericPacket::$packet_type(packet) => Ok(packet), + crate::packets::GenericPacket::$packet_type(packet) => Ok(packet), _ => Err(()), } } diff --git a/crates/composition-protocol/src/packets/serverbound/handshake.rs b/crates/composition-protocol/src/packets/serverbound/handshake.rs new file mode 100644 index 0000000..7c90e78 --- /dev/null +++ b/crates/composition-protocol/src/packets/serverbound/handshake.rs @@ -0,0 +1,50 @@ +use crate::{mctypes::VarInt, ClientState}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SH00Handshake { + pub protocol_version: VarInt, + pub server_address: String, + pub server_port: u16, + pub next_state: ClientState, +} +crate::packets::packet!( + SH00Handshake, + 0x00, + ClientState::Handshake, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SH00Handshake> { + let (data, protocol_version) = VarInt::parse(data)?; + let (data, server_address) = String::parse(data)?; + let (data, server_port) = u16::parse(data)?; + let (data, next_state) = VarInt::parse(data)?; + + Ok(( + data, + SH00Handshake { + protocol_version, + server_address, + server_port, + next_state: match *next_state { + 1 => ClientState::Status, + 2 => ClientState::Login, + _ => todo!("Invalid next state"), + }, + }, + )) + }, + |packet: &SH00Handshake| -> Vec { + let mut output = vec![]; + output.extend(packet.protocol_version.serialize()); + output.extend(packet.server_address.serialize()); + output.extend(packet.server_port.serialize()); + output.extend( + VarInt::from(match packet.next_state { + ClientState::Status => 0x01, + ClientState::Login => 0x02, + _ => panic!("invalid SH00Handshake next_state"), + }) + .serialize(), + ); + output + } +); diff --git a/crates/composition-protocol/src/packets/serverbound/login.rs b/crates/composition-protocol/src/packets/serverbound/login.rs new file mode 100644 index 0000000..208400a --- /dev/null +++ b/crates/composition-protocol/src/packets/serverbound/login.rs @@ -0,0 +1,118 @@ +use crate::mctypes::{Uuid, VarInt}; +use composition_parsing::take_bytes; + +#[derive(Clone, Debug, PartialEq)] +pub struct SL00LoginStart { + pub name: String, + pub uuid: Option, +} +crate::packets::packet!( + SL00LoginStart, + 0x00, + crate::ClientState::Login, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SL00LoginStart> { + let (data, name) = String::parse(data)?; + let (data, has_uuid) = bool::parse(data)?; + if has_uuid { + let (data, uuid) = Uuid::parse(data)?; + Ok(( + data, + SL00LoginStart { + name, + uuid: Some(uuid), + }, + )) + } else { + Ok((data, SL00LoginStart { name, uuid: None })) + } + }, + |packet: &SL00LoginStart| -> Vec { + let mut output = vec![]; + output.extend(packet.name.serialize()); + output.extend(packet.uuid.is_some().serialize()); + if let Some(uuid) = packet.uuid { + output.extend(uuid.serialize()); + } + output + } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct SL01EncryptionResponse { + pub shared_secret: Vec, + pub verify_token: Vec, +} +crate::packets::packet!( + SL01EncryptionResponse, + 0x01, + crate::ClientState::Login, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SL01EncryptionResponse> { + let (data, shared_secret_len) = VarInt::parse(data)?; + let (data, shared_secret) = take_bytes(*shared_secret_len as usize)(data)?; + let (data, verify_token_len) = VarInt::parse(data)?; + let (data, verify_token) = take_bytes(*verify_token_len as usize)(data)?; + + Ok(( + data, + SL01EncryptionResponse { + shared_secret: shared_secret.to_vec(), + verify_token: verify_token.to_vec(), + }, + )) + }, + |packet: &SL01EncryptionResponse| -> Vec { + let mut output = vec![]; + output.extend(VarInt::from(packet.shared_secret.len() as i32).serialize()); + output.extend(&packet.shared_secret); + output.extend(VarInt::from(packet.verify_token.len() as i32).serialize()); + output.extend(&packet.verify_token); + output + } +); + +#[derive(Clone, Debug, PartialEq)] +pub struct SL02LoginPluginResponse { + pub message_id: VarInt, + pub successful: bool, + pub data: Vec, +} +crate::packets::packet!( + SL02LoginPluginResponse, + 0x02, + crate::ClientState::Login, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SL02LoginPluginResponse> { + let (data, message_id) = VarInt::parse(data)?; + let (data, successful) = bool::parse(data)?; + if successful { + Ok(( + &[], + SL02LoginPluginResponse { + message_id, + successful, + data: data.to_vec(), + }, + )) + } else { + Ok(( + data, + SL02LoginPluginResponse { + message_id, + successful, + data: vec![], + }, + )) + } + }, + |packet: &SL02LoginPluginResponse| -> Vec { + let mut output = vec![]; + output.extend(packet.message_id.serialize()); + output.extend(packet.successful.serialize()); + if packet.successful { + output.extend(&packet.data); + } + output + } +); diff --git a/crates/composition-protocol/src/packet/serverbound/mod.rs b/crates/composition-protocol/src/packets/serverbound/mod.rs similarity index 100% rename from crates/composition-protocol/src/packet/serverbound/mod.rs rename to crates/composition-protocol/src/packets/serverbound/mod.rs diff --git a/crates/composition-protocol/src/packets/serverbound/play.rs b/crates/composition-protocol/src/packets/serverbound/play.rs new file mode 100644 index 0000000..e873b5c --- /dev/null +++ b/crates/composition-protocol/src/packets/serverbound/play.rs @@ -0,0 +1,140 @@ +use crate::{ + entities::{EntityPosition, EntityRotation}, + mctypes::VarInt, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SP08CommandSuggestionsRequest { + pub transaction_id: VarInt, + pub text: String, +} +crate::packets::packet!( + SP08CommandSuggestionsRequest, + 0x08, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SP08CommandSuggestionsRequest> { + let (data, transaction_id) = VarInt::parse(data)?; + let (data, text) = String::parse(data)?; + Ok(( + data, + SP08CommandSuggestionsRequest { + transaction_id, + text, + }, + )) + }, + |packet: &SP08CommandSuggestionsRequest| -> Vec { + let mut output = vec![]; + output.extend(packet.transaction_id.serialize()); + output.extend(packet.text.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SP11KeepAlive { + pub payload: i64, +} +crate::packets::packet!( + SP11KeepAlive, + 0x11, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SP11KeepAlive> { + let (data, payload) = i64::parse(data)?; + Ok((data, SP11KeepAlive { payload })) + }, + |packet: &SP11KeepAlive| -> Vec { packet.payload.serialize() } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SP13SetPlayerPosition { + pub position: EntityPosition, + pub on_ground: bool, +} +crate::packets::packet!( + SP13SetPlayerPosition, + 0x13, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SP13SetPlayerPosition> { + let (data, position) = EntityPosition::parse(data)?; + let (data, on_ground) = bool::parse(data)?; + Ok(( + data, + SP13SetPlayerPosition { + position, + on_ground, + }, + )) + }, + |packet: &SP13SetPlayerPosition| -> Vec { + let mut output = vec![]; + output.extend(packet.position.serialize()); + output.extend(packet.on_ground.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SP14SetPlayerPositionAndRotation { + pub position: EntityPosition, + pub rotation: EntityRotation, + pub on_ground: bool, +} +crate::packets::packet!( + SP14SetPlayerPositionAndRotation, + 0x14, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SP14SetPlayerPositionAndRotation> { + let (data, position) = EntityPosition::parse(data)?; + let (data, rotation) = EntityRotation::parse(data)?; + let (data, on_ground) = bool::parse(data)?; + Ok(( + data, + SP14SetPlayerPositionAndRotation { + position, + rotation, + on_ground, + }, + )) + }, + |packet: &SP14SetPlayerPositionAndRotation| -> Vec { + let mut output = vec![]; + output.extend(packet.position.serialize()); + output.extend(packet.rotation.serialize()); + output.extend(packet.on_ground.serialize()); + output + } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SP15SetPlayerRotation { + pub rotation: EntityRotation, + pub on_ground: bool, +} +crate::packets::packet!( + SP15SetPlayerRotation, + 0x15, + crate::ClientState::Play, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SP15SetPlayerRotation> { + let (data, rotation) = EntityRotation::parse(data)?; + let (data, on_ground) = bool::parse(data)?; + Ok(( + data, + SP15SetPlayerRotation { + rotation, + on_ground, + }, + )) + }, + |packet: &SP15SetPlayerRotation| -> Vec { + let mut output = vec![]; + output.extend(packet.rotation.serialize()); + output.extend(packet.on_ground.serialize()); + output + } +); diff --git a/crates/composition-protocol/src/packets/serverbound/status.rs b/crates/composition-protocol/src/packets/serverbound/status.rs new file mode 100644 index 0000000..551c480 --- /dev/null +++ b/crates/composition-protocol/src/packets/serverbound/status.rs @@ -0,0 +1,28 @@ +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SS00StatusRequest; +crate::packets::packet!( + SS00StatusRequest, + 0x00, + crate::ClientState::Status, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SS00StatusRequest> { + Ok((data, SS00StatusRequest)) + }, + |_packet: &SS00StatusRequest| -> Vec { vec![] } +); + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct SS01PingRequest { + pub payload: i64, +} +crate::packets::packet!( + SS01PingRequest, + 0x01, + crate::ClientState::Status, + true, + |data: &'data [u8]| -> composition_parsing::ParseResult<'data, SS01PingRequest> { + let (data, payload) = i64::parse(data)?; + Ok((data, SS01PingRequest { payload })) + }, + |packet: &SS01PingRequest| -> Vec { packet.payload.serialize() } +); diff --git a/crates/composition-protocol/src/util.rs b/crates/composition-protocol/src/util.rs deleted file mode 100644 index 28c3847..0000000 --- a/crates/composition-protocol/src/util.rs +++ /dev/null @@ -1,251 +0,0 @@ -use crate::ProtocolError; -use byteorder::{BigEndian, ReadBytesExt}; -use tracing::trace; - -pub type ParseResult<'data, T> = crate::Result<(&'data [u8], T)>; - -pub fn take_bytes(num: usize) -> impl Fn(&'_ [u8]) -> ParseResult<'_, &'_ [u8]> { - move |data| { - use std::cmp::Ordering; - - match data.len().cmp(&num) { - Ordering::Greater => Ok((&data[num..], &data[..num])), - Ordering::Equal => Ok((&[], data)), - Ordering::Less => Err(ProtocolError::NotEnoughData), - } - } -} - -#[tracing::instrument] -pub fn parse_varint(data: &[u8]) -> ParseResult<'_, i32> { - trace!("{:?}", data); - let mut output = 0u32; - let mut bytes_read = 0; - - for i in 0..=5 { - if i == 5 { - // VarInts can only have 5 bytes maximum. - return Err(ProtocolError::InvalidData); - } else if data.len() <= i { - return Err(ProtocolError::NotEnoughData); - } - - let byte = data[i]; - output |= ((byte & 0x7f) as u32) << (7 * i); - - if byte & 0x80 != 0x80 { - // We found the last byte of the VarInt. - bytes_read = i + 1; - break; - } - } - - Ok((&data[bytes_read..], output as i32)) -} -#[tracing::instrument] -pub fn serialize_varint(value: i32) -> Vec { - let mut value = value as u32; - let mut output = vec![]; - loop { - let data = (value & 0x7f) as u8; - value >>= 7; - - if value == 0 { - output.push(data); - break; - } else { - output.push(data | 0x80); - } - } - output -} - -#[tracing::instrument] -pub fn parse_string(data: &[u8]) -> ParseResult<'_, String> { - let (data, len) = parse_varint(data)?; - let (data, str_bytes) = take_bytes(len as usize)(data)?; - let s = String::from_utf8_lossy(str_bytes).to_string(); - Ok((data, s)) -} -#[tracing::instrument] -pub fn serialize_string(value: &str) -> Vec { - let mut output = vec![]; - output.extend_from_slice(&serialize_varint(value.len() as i32)); - output.extend_from_slice(value.as_bytes()); - output -} - -#[tracing::instrument] -pub fn parse_json(data: &[u8]) -> ParseResult<'_, crate::Json> { - trace!("parse_json: {:?}", data); - let (data, json) = parse_string(data)?; - let json = serde_json::from_str(&json)?; - Ok((data, json)) -} -#[tracing::instrument] -pub fn serialize_json(value: &crate::Json) -> Vec { - trace!("serialize_json: {:?}", value); - serialize_string(&serde_json::to_string(value).expect("valid json")) -} - -#[tracing::instrument] -pub fn parse_chat(data: &[u8]) -> ParseResult<'_, crate::Chat> { - trace!("parse_chat: {:?}", data); - parse_json(data) -} -#[tracing::instrument] -pub fn serialize_chat(value: &crate::Chat) -> Vec { - trace!("serialize_chat: {:?}", value); - serialize_json(value) -} - -#[tracing::instrument] -pub fn parse_uuid(data: &[u8]) -> ParseResult<'_, crate::Uuid> { - trace!("parse_uuid: {:?}", data); - let (data, mut bytes) = take_bytes(16)(data)?; - let uuid = bytes - .read_u128::() - .map_err(|_| ProtocolError::NotEnoughData)?; - Ok((data, uuid)) -} -#[tracing::instrument] -pub fn serialize_uuid(value: &crate::Uuid) -> Vec { - trace!("serialize_uuid: {:?}", value); - value.to_be_bytes().to_vec() -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Position { - pub x: i32, - pub y: i32, - pub z: i32, -} -impl Position { - #[tracing::instrument] - pub fn new(x: i32, y: i32, z: i32) -> Self { - Position { x, y, z } - } - #[tracing::instrument] - pub fn parse(data: &[u8]) -> ParseResult<'_, Self> { - trace!("Position::parse: {:?}", data); - let (data, mut bytes) = take_bytes(8)(data)?; - let i = bytes - .read_i64::() - .map_err(|_| ProtocolError::NotEnoughData)?; - - // x: i26, z: i26, y: i12 - let x = i >> 38; - let mut y = i & 0xFFF; - if y >= 0x800 { - y -= 0x1000; - } - let z = i << 26 >> 38; - - Ok((data, Position::new(x as i32, y as i32, z as i32))) - } - #[tracing::instrument] - pub fn serialize(&self) -> Vec { - trace!("Position::serialize: {:?}", self); - let i: i64 = ((self.x as i64 & 0x3FF_FFFF) << 38) - | ((self.z as i64 & 0x3FF_FFFF) << 12) - | (self.y as i64 & 0xFFF); - i.to_be_bytes().to_vec() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn take_bytes_works() { - let data: [u8; 5] = [0, 1, 2, 3, 4]; - - assert_eq!(take_bytes(3)(&data).unwrap(), (&data[3..], &data[..3])); - assert_eq!(take_bytes(1)(&data).unwrap().0.len(), data.len() - 1); - assert_eq!(take_bytes(1)(&data).unwrap().0[0], 1); - assert_eq!(take_bytes(1)(&[0, 1]).unwrap().0.len(), 1); - assert_eq!(take_bytes(1)(&[1]).unwrap().0.len(), 0); - assert!(take_bytes(1)(&[]).is_err()); - } - - fn get_varints() -> Vec<(i32, Vec)> { - vec![ - (0, vec![0x00]), - (1, vec![0x01]), - (2, vec![0x02]), - (16, vec![0x10]), - (127, vec![0x7f]), - (128, vec![0x80, 0x01]), - (255, vec![0xff, 0x01]), - (25565, vec![0xdd, 0xc7, 0x01]), - (2097151, vec![0xff, 0xff, 0x7f]), - (2147483647, vec![0xff, 0xff, 0xff, 0xff, 0x07]), - (-1, vec![0xff, 0xff, 0xff, 0xff, 0x0f]), - (-2147483648, vec![0x80, 0x80, 0x80, 0x80, 0x08]), - ] - } - #[test] - fn parse_varint_works() { - for (value, bytes) in get_varints() { - assert_eq!(value, parse_varint(&bytes).unwrap().1); - } - } - #[test] - fn serialize_varint_works() { - for (value, bytes) in get_varints() { - assert_eq!(bytes, serialize_varint(value)); - } - } - - fn get_strings() -> Vec<(&'static str, Vec)> { - let s_127 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456"; - vec![ - ("", vec![0x00]), - ("A", vec![0x01, 0x41]), - ("AB", vec![0x02, 0x41, 0x42]), - (s_127, { - let mut v = vec![0x7f]; - v.extend_from_slice(s_127.as_bytes()); - v - }), - ] - } - #[test] - fn parse_string_works() { - for (value, bytes) in get_strings() { - assert_eq!(value, parse_string(&bytes).unwrap().1); - } - } - #[test] - fn serialize_string_works() { - for (value, bytes) in get_strings() { - assert_eq!(bytes, serialize_string(value)); - } - } - - fn get_positions() -> Vec<(Position, Vec)> { - vec![ - // x: 01000110000001110110001100 z: 10110000010101101101001000 y: 001100111111 - ( - Position::new(18357644, 831, -20882616), - vec![ - 0b01000110, 0b00000111, 0b01100011, 0b00101100, 0b00010101, 0b10110100, - 0b10000011, 0b00111111, - ], - ), - ] - } - #[test] - fn parse_position_works() { - for (value, bytes) in get_positions() { - assert_eq!(value, Position::parse(&bytes).unwrap().1); - } - } - #[test] - fn serialize_position_works() { - for (value, bytes) in get_positions() { - assert_eq!(bytes, value.serialize()); - } - } -} diff --git a/crates/composition-world/Cargo.toml b/crates/composition-world/Cargo.toml new file mode 100644 index 0000000..a9ce663 --- /dev/null +++ b/crates/composition-world/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "composition-world" +version = "0.1.0" +edition = "2021" +authors = ["Garen Tyler "] +description = "A Minecraft world generator implementation that allows for custom worlds" +license = "MIT" + +[dependencies] +anyhow = { workspace = true } +async-trait = { workspace = true } +composition-protocol = { workspace = true } +thiserror = { workspace = true } diff --git a/crates/composition-world/src/chunks.rs b/crates/composition-world/src/chunks.rs new file mode 100644 index 0000000..01cce91 --- /dev/null +++ b/crates/composition-world/src/chunks.rs @@ -0,0 +1,44 @@ +use crate::{ + blocks::{Block, BlockPosition}, + entities::{Entity, EntityId, EntityPosition}, +}; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct Chunk { + // blocks[x][y][z] + pub blocks: [[[Block; 16]; 320]; 16], + pub entities: HashMap, +} +impl Default for Chunk { + fn default() -> Self { + Chunk { + blocks: [[[Block::default(); 16]; 320]; 16], + entities: HashMap::new(), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Default, Eq, Hash)] +pub struct ChunkPosition { + pub x: i32, + pub z: i32, +} +impl From for ChunkPosition { + fn from(value: BlockPosition) -> Self { + // Divide by 16 to get the chunk. + ChunkPosition { + x: value.x >> 4, + z: value.z >> 4, + } + } +} +impl From for ChunkPosition { + fn from(value: EntityPosition) -> Self { + // Divide by 16 and convert to i32. + ChunkPosition { + x: (value.x / 16.0) as i32, + z: (value.z / 16.0) as i32, + } + } +} diff --git a/crates/composition-world/src/generators/mod.rs b/crates/composition-world/src/generators/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/composition-world/src/generators/mod.rs @@ -0,0 +1 @@ + diff --git a/crates/composition-world/src/lib.rs b/crates/composition-world/src/lib.rs new file mode 100644 index 0000000..836b558 --- /dev/null +++ b/crates/composition-world/src/lib.rs @@ -0,0 +1,54 @@ +pub mod chunks; +pub mod generators; + +pub use composition_protocol::{blocks, entities}; + +use crate::chunks::ChunkPosition; +use blocks::BlockPosition; +use std::path::Path; +use thiserror::Error; + +#[async_trait::async_trait] +pub trait World { + /// Get the world's name. + fn name() -> String; + /// Create a new world. + fn new(seed: u128) -> Self; + /// Load an existing world. + async fn load_from_dir + Send>(world_dir: P) -> Result + where + Self: Sized; + /// Save the world to a directory. + async fn save_to_dir + Send>(&self, world_dir: P) -> Result<()>; + + async fn is_chunk_loaded(&self, chunk_pos: ChunkPosition) -> bool; + async fn load_chunk(&self, chunk_pos: ChunkPosition) -> Result<()>; + async fn unload_chunk(&self, chunk_pos: ChunkPosition) -> Result<()>; + async fn get_chunk(&self, chunk_pos: ChunkPosition) -> Result; + async fn set_chunk(&self, chunk_pos: ChunkPosition, chunk: chunks::Chunk) -> Result<()>; + + // Getting/setting blocks requires async because the chunk might not be loaded. + async fn get_block(&self, block_pos: BlockPosition) -> Result; + async fn set_block(&self, block_pos: BlockPosition, block: blocks::Block) -> Result<()>; + + // Spawning/removing entities requires async because the chunk might not be loaded. + async fn spawn_entity( + &self, + entity_pos: entities::EntityPosition, + entity: entities::Entity, + ) -> Result; + fn get_entity(&self, entity_id: entities::EntityId) -> Result<&entities::Entity>; + fn get_entity_mut(&self, entity_id: entities::EntityId) -> Result<&mut entities::Entity>; + async fn remove_entity(&self, entity_id: entities::EntityId) -> Result<()>; +} + +#[derive(Error, Debug)] +pub enum WorldError { + #[error("the given position was out of bounds")] + OutOfBounds, + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + Other(#[from] anyhow::Error), +} +pub type Result = std::result::Result; diff --git a/src/config.rs b/src/config.rs index b4418f0..6cdfe91 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,7 +33,9 @@ pub struct Config { pub server_icon: PathBuf, #[serde(skip)] pub server_icon_bytes: Vec, + #[serde(skip)] pub protocol_version: i32, + #[serde(skip)] pub game_version: String, #[serde(skip)] pub server_version: String, diff --git a/src/lib.rs b/src/lib.rs index e206d72..a88b70e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ pub async fn start_server() -> (server::Server, tokio_util::sync::CancellationTo pub mod prelude { pub use crate::config::Config; pub use crate::START_TIME; - pub use composition_protocol::{Chat, Json, Uuid}; + pub use composition_protocol::mctypes::{Chat, Json, Uuid}; pub use serde::{Deserialize, Serialize}; pub use serde_json::json; pub use std::collections::VecDeque; diff --git a/src/net.rs b/src/net.rs index 33f9b15..83b84d1 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,18 +1,21 @@ use crate::prelude::*; -use composition_protocol::{packet::GenericPacket, ClientState, ProtocolError}; +use composition_protocol::packets::serverbound::SL00LoginStart; +use composition_protocol::{packets::GenericPacket, ClientState, ProtocolError}; use std::sync::Arc; use std::time::Instant; use tokio::net::TcpStream; use tokio::sync::RwLock; -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum NetworkClientState { Handshake, Status { received_request: bool, received_ping: bool, }, - Login, + Login { + received_start: (bool, Option), + }, Play, Disconnected, } @@ -20,11 +23,8 @@ impl From for ClientState { fn from(value: NetworkClientState) -> Self { match value { NetworkClientState::Handshake => ClientState::Handshake, - NetworkClientState::Status { - received_request: _, - received_ping: _, - } => ClientState::Status, - NetworkClientState::Login => ClientState::Login, + NetworkClientState::Status { .. } => ClientState::Status, + NetworkClientState::Login { .. } => ClientState::Login, NetworkClientState::Play => ClientState::Play, NetworkClientState::Disconnected => ClientState::Disconnected, } @@ -34,11 +34,8 @@ impl AsRef for NetworkClientState { fn as_ref(&self) -> &ClientState { match self { NetworkClientState::Handshake => &ClientState::Handshake, - NetworkClientState::Status { - received_request: _, - received_ping: _, - } => &ClientState::Status, - NetworkClientState::Login => &ClientState::Login, + NetworkClientState::Status { .. } => &ClientState::Status, + NetworkClientState::Login { .. } => &ClientState::Login, NetworkClientState::Play => &ClientState::Play, NetworkClientState::Disconnected => &ClientState::Disconnected, } @@ -110,7 +107,7 @@ impl NetworkClient { let mut bytes_consumed = 0; while !data.is_empty() { - let p = GenericPacket::parse_uncompressed(self.state.into(), true, data); + let p = GenericPacket::parse_uncompressed(self.state.clone().into(), true, data); trace!("{} got {:?}", self.id, p); match p { Ok((d, packet)) => { @@ -119,11 +116,11 @@ impl NetworkClient { data = d; self.incoming_packet_queue.push_back(packet); } - Err(ProtocolError::NotEnoughData) => break, + Err(composition_parsing::Error::Eof) => break, Err(e) => { // Remove the valid bytes before this packet. self.incoming_data = self.incoming_data.split_off(bytes_consumed); - return Err(e); + return Err(e.into()); } } } @@ -170,12 +167,12 @@ impl NetworkClient { &self, packet: P, ) -> tokio::io::Result<()> { - use composition_protocol::util::serialize_varint; + use composition_parsing::Parsable; let packet: GenericPacket = packet.into(); debug!("Sending packet {:?} to client {}", packet, self.id); let (packet_id, mut packet_body) = packet.serialize(); - let mut packet_id = serialize_varint(packet_id); + let mut packet_id = packet_id.serialize(); // TODO: Stream compression/encryption. @@ -184,15 +181,14 @@ impl NetworkClient { b.append(&mut packet_body); // bytes: packet length as varint, packet id as varint, packet body - let mut bytes = serialize_varint(b.len() as i32); - bytes.append(&mut b); + let bytes = Parsable::serialize(&b); self.stream.write().await.write_all(&bytes).await?; Ok(()) } #[tracing::instrument] - pub async fn disconnect(&mut self, reason: Option) { - use composition_protocol::packet::clientbound::{CL00Disconnect, CP17Disconnect}; + pub async fn disconnect(&mut self, reason: Option) { + use composition_protocol::packets::clientbound::{CL00Disconnect, CP17Disconnect}; let reason = reason.unwrap_or(json!({ "text": "You have been disconnected!" })); diff --git a/src/server/mod.rs b/src/server/mod.rs index 93a05d2..ff0d505 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -168,13 +168,13 @@ impl Server { .filter(|client| matches!(client.state, NetworkClientState::Play)) .count(); 'clients: for client in clients.iter_mut() { - use composition_protocol::packet::{clientbound::*, serverbound::*}; + use composition_protocol::packets::{clientbound::*, serverbound::*}; 'packets: while !client.incoming_packet_queue.is_empty() { // client.read_packet() // None: The client doesn't have any more packets. // Some(Err(_)): The client read an unexpected packet. TODO: Handle this error. // Some(Ok(_)): The client read the expected packet. - match client.state { + match client.state.clone() { NetworkClientState::Handshake => { let handshake = match client.read_packet::() { None => continue 'packets, @@ -188,7 +188,9 @@ impl Server { received_ping: false, }; } else if handshake.next_state == ClientState::Login { - client.state = NetworkClientState::Login; + client.state = NetworkClientState::Login { + received_start: (false, None), + }; } else { client .disconnect(Some( @@ -245,7 +247,25 @@ impl Server { client.state = NetworkClientState::Disconnected; } NetworkClientState::Status { .. } => unreachable!(), - NetworkClientState::Login => unimplemented!(), + NetworkClientState::Login { received_start, .. } if !received_start.0 => { + let login_start = match client.read_packet::() { + None => continue 'packets, + Some(Err(_)) => continue 'clients, + Some(Ok(p)) => p, + }; + // TODO: Authenticate the user. + // TODO: Get the user from the stored database. + // TODO: Encryption/compression. + client.queue_packet(CL02LoginSuccess { + uuid: login_start.uuid.unwrap_or(0u128), + username: login_start.name.clone(), + properties: vec![], + }); + client.state = NetworkClientState::Login { + received_start: (true, Some(login_start)), + }; + } + NetworkClientState::Login { .. } => unreachable!(), NetworkClientState::Play => unimplemented!(), NetworkClientState::Disconnected => unimplemented!(), }