Implement more of the protocol
This commit is contained in:
parent
60e54ed2cc
commit
d385deb1bb
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -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"
|
||||
|
17
Cargo.toml
17
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 }
|
||||
|
13
crates/composition-parsing/Cargo.toml
Normal file
13
crates/composition-parsing/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "composition-parsing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
description = "Useful shared parsing functions"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
byteorder = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
16
crates/composition-parsing/src/error.rs
Normal file
16
crates/composition-parsing/src/error.rs
Normal file
@ -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<T> = std::result::Result<T, Error>;
|
||||
pub type ParseResult<'data, T> = Result<(&'data [u8], T)>;
|
78
crates/composition-parsing/src/lib.rs
Normal file
78
crates/composition-parsing/src/lib.rs
Normal file
@ -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<i32> for VarInt {
|
||||
fn from(value: i32) -> Self {
|
||||
VarInt(value)
|
||||
}
|
||||
}
|
||||
impl From<VarInt> for i32 {
|
||||
fn from(value: VarInt) -> Self {
|
||||
*value
|
||||
}
|
||||
}
|
||||
impl From<usize> 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());
|
||||
}
|
||||
}
|
372
crates/composition-parsing/src/parsable.rs
Normal file
372
crates/composition-parsing/src/parsable.rs
Normal file
@ -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<u8>;
|
||||
|
||||
fn parse_optional(data: &[u8]) -> ParseResult<'_, Option<Self>>
|
||||
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<Self>>
|
||||
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<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let (data, vec_len) = VarInt::parse(data)?;
|
||||
Self::parse_repeated(*vec_len as usize, data)
|
||||
}
|
||||
}
|
||||
impl<T: Parsable + std::fmt::Debug> Parsable for Option<T> {
|
||||
#[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<u8> {
|
||||
match self {
|
||||
Some(t) => {
|
||||
let mut output = vec![];
|
||||
output.extend(true.serialize());
|
||||
output.extend(t.serialize());
|
||||
output
|
||||
}
|
||||
None => false.serialize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: Parsable + std::fmt::Debug> Parsable for Vec<T> {
|
||||
#[tracing::instrument]
|
||||
fn parse(data: &[u8]) -> ParseResult<'_, Self> {
|
||||
T::parse_vec(data)
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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::<BigEndian>().map_err(|_| Error::Eof)?;
|
||||
Ok((data, i))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
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<u8> {
|
||||
if *self {
|
||||
vec![0x01]
|
||||
} else {
|
||||
vec![0x00]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn get_varints() -> Vec<(i32, Vec<u8>)> {
|
||||
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<u8>)> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = []
|
||||
|
35
crates/composition-protocol/src/blocks/mod.rs
Normal file
35
crates/composition-protocol/src/blocks/mod.rs
Normal file
@ -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
|
||||
}
|
16
crates/composition-protocol/src/entities/cat.rs
Normal file
16
crates/composition-protocol/src/entities/cat.rs
Normal file
@ -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
|
||||
}
|
14
crates/composition-protocol/src/entities/frog.rs
Normal file
14
crates/composition-protocol/src/entities/frog.rs
Normal file
@ -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
|
||||
}
|
91
crates/composition-protocol/src/entities/metadata.rs
Normal file
91
crates/composition-protocol/src/entities/metadata.rs
Normal file
@ -0,0 +1,91 @@
|
||||
use crate::{
|
||||
blocks::BlockFace,
|
||||
mctypes::{Chat, Position, Uuid, VarInt},
|
||||
};
|
||||
|
||||
pub type EntityMetadata = Vec<EntityMetadataEntry>;
|
||||
|
||||
#[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<Chat>) = 6,
|
||||
// TODO: Add Slot type
|
||||
Slot(()) = 7,
|
||||
Boolean(bool) = 8,
|
||||
Rotation {
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
} = 9,
|
||||
Position(Position) = 10,
|
||||
OptionalPosition(Option<Position>) = 11,
|
||||
Direction(BlockFace) = 12,
|
||||
OptionalUuid(Uuid) = 13,
|
||||
BlockId(VarInt) = 14,
|
||||
// 0 or None means air
|
||||
OptionalBlockId(Option<VarInt>) = 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,
|
||||
}
|
137
crates/composition-protocol/src/entities/mod.rs
Normal file
137
crates/composition-protocol/src/entities/mod.rs
Normal file
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<Chat>,
|
||||
}
|
||||
#[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<VarInt>,
|
||||
pub is_potion_effect_ambient: bool,
|
||||
pub arrow_count: VarInt,
|
||||
pub bee_stingers: VarInt,
|
||||
pub currently_sleeping_bed_position: Option<BlockPosition>,
|
||||
}
|
||||
|
||||
#[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<Uuid>,
|
||||
}
|
132
crates/composition-protocol/src/entities/particle.rs
Normal file
132
crates/composition-protocol/src/entities/particle.rs
Normal file
@ -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 },
|
||||
}
|
24
crates/composition-protocol/src/entities/player.rs
Normal file
24
crates/composition-protocol/src/entities/player.rs
Normal file
@ -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,
|
||||
}
|
19
crates/composition-protocol/src/entities/sniffer.rs
Normal file
19
crates/composition-protocol/src/entities/sniffer.rs
Normal file
@ -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,
|
||||
}
|
41
crates/composition-protocol/src/entities/villager.rs
Normal file
41
crates/composition-protocol/src/entities/villager.rs
Normal file
@ -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,
|
||||
}
|
1092
crates/composition-protocol/src/inventory/mod.rs
Normal file
1092
crates/composition-protocol/src/inventory/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
13
crates/composition-protocol/src/inventory/slot.rs
Normal file
13
crates/composition-protocol/src/inventory/slot.rs
Normal file
@ -0,0 +1,13 @@
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
pub struct Slot {
|
||||
pub contents: Option<ItemStack>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
pub struct ItemStack {
|
||||
// TODO: Item ID
|
||||
pub id: (),
|
||||
pub count: u8,
|
||||
// TODO: NBT
|
||||
pub nbt: (),
|
||||
}
|
@ -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<u8> for Difficulty {
|
||||
type Error = ();
|
||||
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
|
||||
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),
|
||||
}
|
||||
|
106
crates/composition-protocol/src/mctypes.rs
Normal file
106
crates/composition-protocol/src/mctypes.rs
Normal file
@ -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<u8> {
|
||||
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<u8> for Difficulty {
|
||||
type Error = ();
|
||||
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
|
||||
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<u8> {
|
||||
vec![*self as u8]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn get_positions() -> Vec<(Position, Vec<u8>)> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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<u8> { serialize_json(&packet.reason) }
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL01EncryptionRequest {
|
||||
pub server_id: String,
|
||||
pub public_key: Vec<u8>,
|
||||
pub verify_token: Vec<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
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<CL02LoginSuccessProperty>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL02LoginSuccessProperty {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> { serialize_varint(packet.threshold) }
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL04LoginPluginRequest {
|
||||
pub message_id: i32,
|
||||
pub channel: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
@ -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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let y = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let z = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_y = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_z = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.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<u8> {
|
||||
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<u8> {
|
||||
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<u8> { 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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, CP1FKeepAlive { payload }))
|
||||
},
|
||||
|packet: &CP1FKeepAlive| -> Vec<u8> { 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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, location) = Position::parse(data)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let d = bytes
|
||||
.read_i32::<BigEndian>()
|
||||
.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<u8> {
|
||||
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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_y = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(2)(data)?;
|
||||
let velocity_z = bytes
|
||||
.read_i16::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((
|
||||
data,
|
||||
CP50SetEntityVelocity {
|
||||
entity_id,
|
||||
velocity_x,
|
||||
velocity_y,
|
||||
velocity_z,
|
||||
},
|
||||
))
|
||||
},
|
||||
|packet: &CP50SetEntityVelocity| -> Vec<u8> {
|
||||
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::<BigEndian>()
|
||||
.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<u8> {
|
||||
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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
@ -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<u8> { 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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, CS01PingResponse { payload }))
|
||||
},
|
||||
|packet: &CS01PingResponse| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
|
||||
);
|
@ -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::<BigEndian>()
|
||||
.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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
@ -1,115 +0,0 @@
|
||||
use crate::{util::*, Uuid};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SL00LoginStart {
|
||||
pub name: String,
|
||||
pub uuid: Option<Uuid>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
pub verify_token: Vec<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
@ -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<u8> {
|
||||
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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, SP11KeepAlive { payload }))
|
||||
},
|
||||
|packet: &SP11KeepAlive| -> Vec<u8> { 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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let y = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let z = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.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<u8> {
|
||||
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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let y = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(8)(data)?;
|
||||
let z = bytes
|
||||
.read_f64::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let yaw = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let pitch = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.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<u8> {
|
||||
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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
let (data, mut bytes) = take_bytes(4)(data)?;
|
||||
let pitch = bytes
|
||||
.read_f32::<BigEndian>()
|
||||
.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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
@ -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<u8> { 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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, SS01PingRequest { payload }))
|
||||
},
|
||||
|packet: &SS01PingRequest| -> Vec<u8> { packet.payload.to_be_bytes().to_vec() }
|
||||
);
|
164
crates/composition-protocol/src/packets/clientbound/login.rs
Normal file
164
crates/composition-protocol/src/packets/clientbound/login.rs
Normal file
@ -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<u8> { packet.reason.serialize() }
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL01EncryptionRequest {
|
||||
pub server_id: String,
|
||||
pub public_key: Vec<u8>,
|
||||
pub verify_token: Vec<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
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<CL02LoginSuccessProperty>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL02LoginSuccessProperty {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub signature: Option<String>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> { packet.threshold.serialize() }
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CL04LoginPluginRequest {
|
||||
pub message_id: VarInt,
|
||||
pub channel: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend(packet.message_id.serialize());
|
||||
output.extend(packet.channel.serialize());
|
||||
output.extend(&packet.data);
|
||||
output
|
||||
}
|
||||
);
|
283
crates/composition-protocol/src/packets/clientbound/play.rs
Normal file
283
crates/composition-protocol/src/packets/clientbound/play.rs
Normal file
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u8> { 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<u8> { 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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
@ -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<u8> { 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<u8> { packet.payload.serialize() }
|
||||
);
|
@ -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<GenericPacket> + Into<GenericPacket> {
|
||||
const ID: PacketId;
|
||||
pub type PacketId = crate::mctypes::VarInt;
|
||||
|
||||
pub trait Packet:
|
||||
std::fmt::Debug
|
||||
+ Clone
|
||||
+ TryFrom<GenericPacket>
|
||||
+ Into<GenericPacket>
|
||||
+ 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<u8>;
|
||||
}
|
||||
|
||||
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::<GenericPacket>::into(packet))),
|
||||
($packet_type::CLIENT_STATE, $packet_type::ID, $packet_type::IS_SERVERBOUND) => $packet_type::parse(data).map(|(data, packet)| (data, Into::<GenericPacket>::into(packet))),
|
||||
)*
|
||||
_ => Ok((data, Self::UnimplementedPacket(UnimplementedPacket(packet_id)))),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn serialize(&self) -> (crate::packet::PacketId, Vec<u8>) {
|
||||
pub fn serialize(&self) -> (crate::packets::PacketId, Vec<u8>) {
|
||||
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<u8> { 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<u8> {
|
||||
#[tracing::instrument]
|
||||
fn serialize(&self) -> Vec<u8> {
|
||||
$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<crate::packet::GenericPacket> for $packet_type {
|
||||
impl TryFrom<crate::packets::GenericPacket> for $packet_type {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: crate::packet::GenericPacket) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: crate::packets::GenericPacket) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
crate::packet::GenericPacket::$packet_type(packet) => Ok(packet),
|
||||
crate::packets::GenericPacket::$packet_type(packet) => Ok(packet),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
@ -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<u8> {
|
||||
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
|
||||
}
|
||||
);
|
118
crates/composition-protocol/src/packets/serverbound/login.rs
Normal file
118
crates/composition-protocol/src/packets/serverbound/login.rs
Normal file
@ -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<Uuid>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
pub verify_token: Vec<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
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<u8>,
|
||||
}
|
||||
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<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend(packet.message_id.serialize());
|
||||
output.extend(packet.successful.serialize());
|
||||
if packet.successful {
|
||||
output.extend(&packet.data);
|
||||
}
|
||||
output
|
||||
}
|
||||
);
|
140
crates/composition-protocol/src/packets/serverbound/play.rs
Normal file
140
crates/composition-protocol/src/packets/serverbound/play.rs
Normal file
@ -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<u8> {
|
||||
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<u8> { 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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
let mut output = vec![];
|
||||
output.extend(packet.rotation.serialize());
|
||||
output.extend(packet.on_ground.serialize());
|
||||
output
|
||||
}
|
||||
);
|
@ -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<u8> { 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<u8> { packet.payload.serialize() }
|
||||
);
|
@ -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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
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::<BigEndian>()
|
||||
.map_err(|_| ProtocolError::NotEnoughData)?;
|
||||
Ok((data, uuid))
|
||||
}
|
||||
#[tracing::instrument]
|
||||
pub fn serialize_uuid(value: &crate::Uuid) -> Vec<u8> {
|
||||
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::<BigEndian>()
|
||||
.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<u8> {
|
||||
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<u8>)> {
|
||||
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<u8>)> {
|
||||
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<u8>)> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
13
crates/composition-world/Cargo.toml
Normal file
13
crates/composition-world/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "composition-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Garen Tyler <garentyler@garen.dev>"]
|
||||
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 }
|
44
crates/composition-world/src/chunks.rs
Normal file
44
crates/composition-world/src/chunks.rs
Normal file
@ -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<EntityId, (EntityPosition, Entity)>,
|
||||
}
|
||||
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<BlockPosition> 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<EntityPosition> 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,
|
||||
}
|
||||
}
|
||||
}
|
1
crates/composition-world/src/generators/mod.rs
Normal file
1
crates/composition-world/src/generators/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
|
54
crates/composition-world/src/lib.rs
Normal file
54
crates/composition-world/src/lib.rs
Normal file
@ -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<P: AsRef<Path> + Send>(world_dir: P) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
/// Save the world to a directory.
|
||||
async fn save_to_dir<P: AsRef<Path> + 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<chunks::Chunk>;
|
||||
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<blocks::Block>;
|
||||
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<entities::EntityId>;
|
||||
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<T> = std::result::Result<T, WorldError>;
|
@ -33,7 +33,9 @@ pub struct Config {
|
||||
pub server_icon: PathBuf,
|
||||
#[serde(skip)]
|
||||
pub server_icon_bytes: Vec<u8>,
|
||||
#[serde(skip)]
|
||||
pub protocol_version: i32,
|
||||
#[serde(skip)]
|
||||
pub game_version: String,
|
||||
#[serde(skip)]
|
||||
pub server_version: String,
|
||||
|
@ -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;
|
||||
|
40
src/net.rs
40
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<SL00LoginStart>),
|
||||
},
|
||||
Play,
|
||||
Disconnected,
|
||||
}
|
||||
@ -20,11 +23,8 @@ impl From<NetworkClientState> 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<ClientState> 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<composition_protocol::Chat>) {
|
||||
use composition_protocol::packet::clientbound::{CL00Disconnect, CP17Disconnect};
|
||||
pub async fn disconnect(&mut self, reason: Option<composition_protocol::mctypes::Chat>) {
|
||||
use composition_protocol::packets::clientbound::{CL00Disconnect, CP17Disconnect};
|
||||
let reason = reason.unwrap_or(json!({
|
||||
"text": "You have been disconnected!"
|
||||
}));
|
||||
|
@ -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::<SH00Handshake>() {
|
||||
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::<SL00LoginStart>() {
|
||||
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!(),
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user