From e1cd9eac3c223c39a7d3e0ead916037585d25c35 Mon Sep 17 00:00:00 2001 From: nea Date: Mon, 27 Mar 2023 19:35:42 +0200 Subject: More packets --- src/lib.rs | 7 +- src/protocol.rs | 16 +- src/v1_19_3.rs | 529 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 524 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index ffba7ce..445a370 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ -// #![cfg_attr(feature = "bench", feature(test))] -#![feature(test)] -// #![cfg_attr(feature = "gat", feature(generic_associated_types))] -// #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "bench", feature(test))] +#![cfg_attr(feature = "gat", feature(generic_associated_types))] +#![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; diff --git a/src/protocol.rs b/src/protocol.rs index 0b3e4c1..c9d81c2 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -203,7 +203,7 @@ macro_rules! proto_struct { } }; ($bodyt: ident $(<$($g: ident),*>)? { - $($fname: ident: $ftyp: ty ),+ + $($fname: ident: $ftyp: ty ),+ $(,)? }) => { $crate::as_item! { #[derive(Debug, Clone, PartialEq)] @@ -257,7 +257,7 @@ macro_rules! proto_struct { macro_rules! define_protocol { ($version: literal, $packett: ident, $rawpackett: ident, $rawdt: ident, $kindt: ident => { $($nam: ident, $id: literal, $state: ident, $direction: ident => $body: ident { - $($fnam: ident: $ftyp: ty),* }),* + $($fnam: ident: $ftyp: ty),* $(,)?}),* $(,)? } ) => { $crate::as_item! { @@ -494,7 +494,7 @@ macro_rules! instead_of_ident { #[macro_export] macro_rules! proto_enum_with_type { - ($typ: ty, $typname: ident, $(($bval: literal, $nam: ident $(($bod: ty))?)),*) => { + ($typ: ty, $typname: ident, $(($bval: literal, $nam: ident $(($bod: ty))?)),* $(,)?) => { $crate::as_item! { #[derive(PartialEq, Clone, Debug)] pub enum $typname { @@ -578,28 +578,28 @@ macro_rules! proto_enum_with_type { #[macro_export] macro_rules! proto_byte_enum { - ($typname: ident, $($bval: literal :: $nam: ident $(($bod: ty))?),*) => { + ($typname: ident, $($bval: literal :: $nam: ident $(($bod: ty))?),* $(,)?) => { proto_enum_with_type!(u8, $typname, $(($bval, $nam $(($bod))?)),*); } } #[macro_export] macro_rules! proto_varint_enum { - ($typname: ident, $($bval: literal :: $nam: ident $(($bod: ty))?),*) => { + ($typname: ident, $($bval: literal :: $nam: ident $(($bod: ty))?),* $(,)?) => { proto_enum_with_type!(VarInt, $typname, $(($bval, $nam $(($bod))?)),*); } } #[macro_export] macro_rules! proto_int_enum { - ($typname: ident, $($bval: literal :: $nam: ident $(($bod: ty))?),*) => { + ($typname: ident, $($bval: literal :: $nam: ident $(($bod: ty))?),* $(,)?) => { proto_enum_with_type!(i32, $typname, $(($bval, $nam $(($bod))?)),*); } } #[macro_export] macro_rules! proto_str_enum { - ($typname: ident, $($sval: literal :: $nam: ident $(($bod: ident))?),*) => { + ($typname: ident, $($sval: literal :: $nam: ident $(($bod: ident))?),* $(,)?) => { crate::as_item! { #[derive(PartialEq, Clone, Debug)] pub enum $typname { @@ -689,7 +689,7 @@ macro_rules! proto_str_enum { #[macro_export] macro_rules! proto_byte_flag { - ($typname: ident, $($bval: literal :: $isnam: ident $setnam: ident),*) => { + ($typname: ident, $($bval: literal :: $isnam: ident $setnam: ident),* $(,)?) => { #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub struct $typname(pub u8); diff --git a/src/v1_19_3.rs b/src/v1_19_3.rs index ee51d04..5495c3d 100644 --- a/src/v1_19_3.rs +++ b/src/v1_19_3.rs @@ -1,17 +1,15 @@ use alloc::{ borrow::ToOwned, - string::{String, ToString}, - vec::Vec, + string::String, }; use alloc::fmt; use fmt::Debug; use crate::{*, types::*, uuid::*}; -pub use crate::v1_19::CommandNode; #[cfg(all(test, feature = "std"))] use crate::protocol::TestRandom; -define_protocol!(760, Packet760, RawPacket760, RawPacket760Body, Packet760Kind => { +define_protocol!(761, Packet761, RawPacket761, RawPacket761Body, Packet761Kind => { // Handshaking Handshake, 0x00, Handshaking, ServerBound => HandshakeSpec { version: VarInt, @@ -137,14 +135,16 @@ define_protocol!(760, Packet760, RawPacket760, RawPacket760Body, Packet760Kind = ClearTitles, 0x0C, Play, ClientBound => ClearTitlesSpec { reset: bool }, + /* CommandSuggestionResponse, 0x0D, Play, ClientBound => CommandSuggestionResponseSpec { transaction_id: VarInt, start: VarInt, length: VarInt, suggestions: CountedArray }, + */ Commands, 0x0E, Play, ClientBound => CommandsSpec { - nodes: CountedArray, + nodes: CountedArray, root_index: VarInt }, CloseContainer, 0x0F, Play, ClientBound => CloseContainerSpec { @@ -215,31 +215,528 @@ define_protocol!(760, Packet760, RawPacket760, RawPacket760Body, Packet760Kind = entity_id: i32 }, InitializeWorldBorder, 0x1E, Play, ClientBound => InitializeWorldBorderSpec { - x: f32, - z: f32, - old_diameter: f32, - new_diameter: f32, + x: f64, + z: f64, + old_diameter: f64, + new_diameter: f64, speed: VarLong, portal_teleport_boundary: VarInt, warning_blocks: VarInt, warning_time: VarInt }, - KeepAlive, 0x1f, Play, ClientBound => KeepAliveSpec { + KeepAliveClientBound, 0x1f, Play, ClientBound => KeepAliveClientBoundSpec { keep_alive_id: i64 }, ChunkDataAndUpdateLight, 0x20, Play, ClientBound => ChunkDataAndUpdateLightSpec { chunk_x: i32, chunk_z: i32, - heightmaps: NamedNbtTag, - chunk_data: CountedArray, - block_entities: CountedArray, - trust_edges: bool // TODO: HEEELP - } + chunk_data: ChunkDataSpec, + light_data: LightDataSpec, + }, + // TODO 0x21 - 0x23 + LoginPlay, 0x24, Play, ClientBound => LoginPlaySpec { + entity_id: i32, + is_hardcore: bool, + gamemode: GameMode, + previous_gamemode: PreviousGameMode, + dimension_names: CountedArray, + registry_codex: NamedNbtTag, + dimension_type: String, + dimension_name: String, + hashed_seed: u64, + max_players: VarInt, + view_distance: VarInt, + simulation_distance: VarInt, + reduced_debug_info: bool, + enable_respawn_screen: bool, + is_debug: bool, + is_flat: bool, + death_location: Option, + }, + // TODO 0x25 - 0x48 + SynchronizePlayerPosition, 0x38, Play, ClientBound => SynchronizePlayerPositionSpec { + position: Vec3, + yaw: f32, + pitch: f32, + flags: SynchronizePlayerPositionFlags, + teleport_id: VarInt, + dismount_vehicle: bool + }, + UpdateRecipeBook, 0x39, Play, ClientBound => UpdateRecipeBookSpec { + action: RecipeBookAction, + }, + // TODO missing recipe + + + SetCenterChunk, 0x4A, Play, ClientBound => SetCenterChunkSpec { + chunk_x: VarInt, + chunk_z: VarInt, + }, + SetDefaultSpawnPosition, 0x4C, Play, ClientBound => SetDefaultSpawnPositionSpec { + location: IntPosition, + angle: f32, + }, + + // TODO missing recipe + + SetHeldItemClient, 0x49, Play, ClientBound => SetHeldItemClientSpec { + selected_slot: u8, + }, + // TODO missing recipe + UpdateRecipes, 0x69, Play, ClientBound => UpdateRecipesSpec { + recipes: CountedArray + }, + + UpdateTags, 0x6A, Play, ClientBound => UpdateTagsSpec { + tags: CountedArray + }, + + // Play - ServerBound + + // TODO 0x00 - 0x06 + + ClientInformation, 0x07, Play, ServerBound => ClientInformationSpec { + locale: String, + view_distance: u8, + chat_mode: ChatMode, + chat_colors: bool, + displayed_skin_parts: DisplayedSkinParts, + main_hand: MainHand, + enable_text_filtering: bool, + allow_server_listings: bool, + }, + // TODO Missing packets + SetHeldItemServer, 0x28, Play, ServerBound => SetHeldItemServerSpec { + selected_slot: u8, + }, }); // Helper types +proto_struct!(ChunkSection { + block_count: u16, + block_state: PalettedContainer, + biomes: PalettedContainer, +}); + +#[derive(Debug, Clone, PartialEq)] +pub struct PalettedContainer { + // TODO: global, linear and hashed palettes + pub singular_value: VarInt, +} + +impl Serialize for PalettedContainer { + fn mc_serialize(&self, to: &mut S) -> SerializeResult { + to.serialize_byte(0)?; + to.serialize_other(&self.singular_value)?; + // Serialize 0 bit bitstorage + to.serialize_other(&BitSet::empty())?; + Ok(()) + } +} + +impl Deserialize for PalettedContainer { + fn mc_deserialize(_data: &[u8]) -> DeserializeResult { + Err(DeserializeErr::CannotUnderstandValue("Not implemented".into())) + } +} + + +pub struct BlobArray { + // TODO make this work same as bit storage with a const generic entry size + pub data: Vec, + entry_size: usize, +} + +impl BlobArray { + pub fn new(entry_size: u8) -> Self { + BlobArray { entry_size: entry_size as usize, data: vec![] } + } + + pub fn ensure_capacity(&mut self, index: usize) { + let entries_per_long = 64usize / self.entry_size; + let needed_length = index / entries_per_long + 1; + if self.data.len() < needed_length { + self.data.extend(vec![0u64; needed_length - self.data.len()]); + } + } + + pub fn set_entry(&mut self, index: usize, value: u64) { + let entries_per_long = 64usize / self.entry_size; + let array_index = index / entries_per_long; + let position = index % entries_per_long; + let array_shift = 64 - (entries_per_long - position) * self.entry_size; + let mask = (1u64 << self.entry_size) - 1; + self.ensure_capacity(index); + self.data[array_index] |= (value & mask) << array_shift; + } + + pub fn get_entry(&mut self, index: usize) -> u64 { + let entries_per_long = 64 / self.entry_size; + let array_index = index / entries_per_long; + let position = index % entries_per_long; + let array_shift = 64 - (entries_per_long - position) * self.entry_size; + let mask = (1u64 << self.entry_size) - 1; + self.ensure_capacity(index); + (self.data[array_index] >> array_shift) & mask + } +} + +proto_struct!(BitSet { + data: CountedArray, +}); + +impl BitSet { + pub fn empty() -> BitSet { + BitSet { data: vec![].into() } + } + + pub fn is_bit_set(&self, index: usize) -> bool { + if index * 64 >= self.data.len() { + return false; + } + self.is_bit_set_unsafe(index) + } + pub fn ensure_capacity(&mut self, index: usize) { + let needed_length = index / 64 + 1; + let length_increase = needed_length - self.data.len(); + if length_increase > 0 { + self.data.extend(vec![0u64; length_increase]); + } + } + + pub fn is_bit_set_unsafe(&self, index: usize) -> bool { + self.data[index / 64] & (1u64 << (index % 64)) != 0 + } + + pub fn set_bit(&mut self, index: usize, value: bool) { + self.ensure_capacity(index); + self.set_bit_unsafe(index, value) + } + + pub fn set_bit_unsafe(&mut self, index: usize, value: bool) { + if value { + self.data[index / 64] |= 1u64 << (index % 64); + } else { + self.data[index / 64] &= !(1u64 << (index % 64)); + } + } +} + + +proto_struct!(LightDataSpec { + trust_edges: bool, + sky_y_mask: BitSet, + block_y_mask: BitSet, + empty_sky_y_mask: BitSet, + empty_block_y_mask: BitSet, + sky_updates: CountedArray, VarInt>, + block_updates: CountedArray, VarInt>, +}); +proto_struct!(ChunkDataSpec { + heightmaps: NamedNbtTag, + buffer: CountedArray, + block_entities: CountedArray, +}); + +proto_byte_flag!(SynchronizePlayerPositionFlags, + 0x01 :: is_x_relative set_x_relative, + 0x02 :: is_y_relative set_y_relative, + 0x04 :: is_z_relative set_z_relative, + 0x08 :: is_y_rot_relative set_y_rot_relative, + 0x10 :: is_x_rot_relative set_x_rot_relative, +); + +proto_varint_enum!(RecipeBookAction, + 0 :: Init(RecipeBookInitSpec), + 1 :: Add(RecipeBookDiffSpec), + 2 :: Remove(RecipeBookDiffSpec), +); + +proto_struct!(RecipeBookInitSpec { + book_settings: BookSettings, + known_recipes: CountedArray, + highlighted_recipes: CountedArray, +}); +proto_struct!(RecipeBookDiffSpec { + book_settings: BookSettings, + to_add_or_remove: CountedArray, +}); +proto_struct!(BookSettings { + crafting: BookSetting, + smelting: BookSetting, + blast_furnace: BookSetting, + smoker: BookSetting, +}); +impl Default for BookSettings { + fn default() -> Self { + return BookSettings { + crafting: BookSetting::default(), + smelting: BookSetting::default(), + blast_furnace: BookSetting::default(), + smoker: BookSetting::default(), + }; + } +} + +proto_struct!(BookSetting { + open: bool, + filter_active: bool, +}); + +impl Default for BookSetting { + fn default() -> Self { + return BookSetting { open: false, filter_active: false }; + } +} + +proto_str_enum!(TagType, + "minecraft:block" :: Block, + "minecraft:item" :: Item, + "minecraft:fluid" :: Fluid, + "minecraft:entity_type" :: EntityType, + "minecraft:game_event" :: GameEvent, +); + +#[derive(Clone, Debug, PartialEq)] +pub struct CommandNodeSpec { + pub children_indices: CountedArray, + pub redirect_node: Option, + pub is_executable: bool, + pub node: CommandNode, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum CommandNode { + Root, + Argument(CommandArgumentNodeSpec), + Literal(CommandLiteralNodeSpec), +} + +proto_struct!(CommandLiteralNodeSpec { + name: String +}); + +#[derive(Clone, Debug, PartialEq)] +pub struct CommandArgumentNodeSpec { + name: String, + parser_id: VarInt, + properties: CommandNodeProperties, + suggestions_types: Option, +} + +impl Serialize for CommandArgumentNodeSpec { + fn mc_serialize(&self, to: &mut S) -> SerializeResult { + to.serialize_other(&self.name)?; + to.serialize_other(&self.parser_id)?; + to.serialize_other(&self.properties)?; + if let Some(body) = &self.suggestions_types { + to.serialize_other(body)?; + } + + Ok(()) + } +} + +impl CommandArgumentNodeSpec { + fn mc_deserialize(has_suggestions: bool, data: &[u8]) -> DeserializeResult { + let Deserialized { value: name, data } = String::mc_deserialize(data)?; + let Deserialized { value: parser_id, data } = VarInt::mc_deserialize(data)?; + let Deserialized { value: properties, data } = CommandNodeProperties::mc_deserialize(data)?; + let (suggestions_types, data) = if has_suggestions { + let Deserialized { value: suggestions_types, data } = SuggestionsTypeSpec::mc_deserialize(data)?; + (Some(suggestions_types), data) + } else { + (None, data) + }; + Deserialized::ok( + Self { + name, + parser_id, + properties, + suggestions_types, + }, + data, + ) + } +} +proto_str_enum!(SuggestionsTypeSpec, + "minecraft:ask_server" :: AskServer, + "minecraft:all_recipes" :: AllRecipes, + "minecraft:available_sounds" :: AvailableSounds, + "minecraft:available_bioms" :: AvailableBiomes, + "minecraft:summonable_entities" :: SummonableEntities, +); + +proto_varint_enum!(CommandNodeProperties, + 0 :: Bool, +); + + +impl Serialize for CommandNodeSpec { + fn mc_serialize(&self, to: &mut S) -> SerializeResult { + let mut flags = 0u8; + use CommandNode::*; + flags |= match &self.node { + Root => 0x00, + Argument(_) => 0x1, + Literal(_) => 0x2, + }; + + if self.is_executable { + flags |= 0x04; + } + if self.redirect_node.is_some() { + flags |= 0x08; + } + + if let Argument(argument_data) = &self.node { + if argument_data.suggestions_types.is_some() { + flags |= 0x10; + } + } + + to.serialize_byte(flags)?; + to.serialize_other(&self.children_indices)?; + if let Some(redirect_node) = &self.redirect_node { + to.serialize_other(redirect_node)?; + } + match &self.node { + Root => Ok(()), + Argument(body) => to.serialize_other(body), + Literal(body) => to.serialize_other(body) + } + } +} + +impl Deserialize for CommandNodeSpec { + fn mc_deserialize(data: &[u8]) -> DeserializeResult { + let Deserialized { value: flags, data } = u8::mc_deserialize(data)?; + let Deserialized { + value: children_indices, + data, + } = >::mc_deserialize(data)?; + let is_executable = flags & 0x04 != 0; + let has_redirect = flags & 0x08 != 0; + let (redirect_node, data) = if has_redirect { + let Deserialized { + value: redirect_node, + data, + } = VarInt::mc_deserialize(data)?; + (Some(redirect_node), data) + } else { + (None, data) + }; + use CommandNode::*; + + let Deserialized { value: node, data } = match flags & 0x03 { + 0x00 => Deserialized::ok(Root, data), + 0x01 => { + Ok(CommandLiteralNodeSpec::mc_deserialize(data)?.map(move |body| Literal(body))) + } + 0x02 => Ok( + CommandArgumentNodeSpec::mc_deserialize(flags & 0x10 != 0, data)? + .map(move |body| Argument(body)), + ), + _ => Err(DeserializeErr::CannotUnderstandValue( + format!("Cannot understand {} as command node type", flags))), + }?; + + Deserialized::ok( + Self { + children_indices, + redirect_node, + is_executable, + node, + }, + data, + ) + } +} + + +proto_struct!(TypedTagList { + tag_type: TagType, + tags: CountedArray +}); + +proto_struct!(TagSpec { + name: String, + entries: CountedArray +}); + +proto_str_enum!(Recipe, + "TODO" :: Todo +); + +proto_varint_enum!(MainHand, + 0 :: Left, + 1 :: Right, +); + +proto_byte_flag!(DisplayedSkinParts, + 0x01 :: is_cape_enabled set_cape_enabled, + 0x02 :: is_jacket_enabled set_jacket_enabled, + 0x04 :: is_left_sleeve_enabled set_left_sleeve_enabled, + 0x08 :: is_right_sleeve_enabled set_right_sleeve_enabled, + 0x10 :: is_left_pants_leg_enabled set_left_pants_leg_enabled, + 0x20 :: is_right_pants_leg_enabled set_right_pants_leg_enabled, + 0x40 :: is_hat_enabled set_hat_enabled, +); + +proto_varint_enum!(ChatMode, + 0 :: Enabled, + 1 :: CommandsOnly, + 2 :: Hidden, +); +proto_struct!(DeathLocation { + death_dimension_name: String, + death_location: IntPosition +}); + +proto_byte_enum!(GameMode, + 0 :: Survival, + 1 :: Creative, + 2 :: Adventure, + 3 :: Spectator, +); + +#[derive(Clone, Debug, PartialEq)] +pub enum PreviousGameMode { + NoPrevious, + Previous(GameMode), +} + +impl PreviousGameMode { + pub fn id(&self) -> i8 { + use PreviousGameMode::*; + match self { + NoPrevious => -1, + Previous(mode) => mode.id() as i8, + } + } +} + +impl Serialize for PreviousGameMode { + fn mc_serialize(&self, to: &mut S) -> SerializeResult { + to.serialize_byte(self.id() as u8) + } +} + +impl Deserialize for PreviousGameMode { + fn mc_deserialize(data: &[u8]) -> DeserializeResult<'_, Self> { + let Deserialized { value: id, data } = i8::mc_deserialize(data)?; + + use PreviousGameMode::*; + match id { + -1 => Deserialized::ok(NoPrevious, data), + other => { + Ok(GameMode::deserialize_with_id(other as u8, data)?.map(move |gm| Previous(gm))) + } + } + } +} proto_struct!(BlockEntity { packed_xz: u8, y: i16, @@ -333,7 +830,7 @@ proto_varint_enum!(BossBarColor, 5 :: Purple, 6 :: White ); -proto_varint_enum!(BossBarDivison, +proto_varint_enum!(BossBarDivision, 0 :: NoDivision, 1 :: SixNotches, 2 :: TenNotches, -- cgit