Implement more of the protocol

This commit is contained in:
Garen Tyler 2023-05-03 21:47:07 -06:00
parent 60e54ed2cc
commit d385deb1bb
Signed by: garentyler
GPG Key ID: D7A048C454CB7054
46 changed files with 3276 additions and 1332 deletions

35
Cargo.lock generated
View File

@ -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"

View File

@ -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 }

View 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 }

View 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)>;

View 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());
}
}

View 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());
}
}
}

View File

@ -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 = []

View 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
}

View 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
}

View 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
}

View 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,
}

View 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>,
}

View 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 },
}

View 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,
}

View 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,
}

View 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,
}

File diff suppressed because it is too large Load Diff

View 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: (),
}

View File

@ -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),
}

View 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());
}
}
}

View File

@ -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
}
);

View File

@ -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
}
);

View File

@ -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() }
);

View File

@ -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
}
);

View File

@ -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
}
);

View File

@ -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
}
);

View File

@ -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() }
);

View 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
}
);

View 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
}
);

View File

@ -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() }
);

View File

@ -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(()),
}
}

View File

@ -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
}
);

View 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
}
);

View 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
}
);

View File

@ -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() }
);

View File

@ -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());
}
}
}

View 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 }

View 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,
}
}
}

View File

@ -0,0 +1 @@

View 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>;

View File

@ -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,

View File

@ -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;

View File

@ -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!"
}));

View File

@ -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!(),
}