use alloc::{ borrow::ToOwned, string::String, }; use alloc::fmt; use fmt::Debug; use crate::{*, types::*, uuid::*}; #[cfg(all(test, feature = "std"))] use crate::protocol::TestRandom; define_protocol!(761, Packet761, RawPacket761, RawPacket761Body, Packet761Kind => { // Handshaking Handshake, 0x00, Handshaking, ServerBound => HandshakeSpec { version: VarInt, server_address: String, server_port: u16, next_state: HandshakeNextState }, // Status - ClientBound StatusRespone, 0x00, Status, ClientBound => StatusResponseSpec { response: super::status::StatusSpec }, PingResponse, 0x01, Status, ClientBound => PingResponseSpec { payload: i64 }, // Status - ServerBound StatusRequest, 0x00, Status, ServerBound => StatusRequestSpec { }, PingRequest, 0x01, Status, ServerBound => PingRequestSpec { payload: i64 }, // Login - ClientBound DisconnectLogin, 0x00, Login, ClientBound => DisconnectLoginSpec { payloadreason: Chat }, EncryptionRequest, 0x01, Login, ClientBound => EncryptionRequestSpec { server_id: String, public_key: CountedArray, verify_token: CountedArray }, LoginSuccess, 0x02, Login, ClientBound => LoginSuccessSpec { uuid: UUID4, username: String, properties: CountedArray }, SetCompression, 0x03, Login, ClientBound => SetCompressionSpec { threshold: VarInt }, LoginPluginRequest, 0x04, Login, ClientBound => LoginPluginRequestSpec { message_id: VarInt, channel: String, data: RemainingBytes }, // Login - ServerBound LoginStart, 0x00, Login, ServerBound => LoginStartSpec { name: String, player_uuid: Option }, EncryptionResponse, 0x01, Login, ServerBound => EncryptionResponseSpec { shared_secret: CountedArray, verfiy_token: CountedArray }, LoginPluginResponse, 0x02, Login, ServerBound => LoginPluginResponseSpec { message_id: VarInt, successful: bool, data: RemainingBytes }, // Play - ClientBound SpawnEntity, 0x00, Play, ClientBound => SpawnEntitySpec { entity_id: VarInt, entity_uuid: UUID4, entity_type: VarInt, // TODO enumerate mob types position: Vec3, pitch: Angle, yaw: Angle, data: i32, velocity: Vec3 }, SpawnExperienceOrb, 0x01, Play, ClientBound => SpawnExperienceOrbSpec { entity_id: VarInt, position: Vec3, count: i16 }, SpawnPlayer, 0x02, Play, ClientBound => SpawnPlayerSpec { entity_id: VarInt, player_uuid: UUID4, position: Vec3, yaw: Angle, pitch: Angle }, EntityAnimation, 0x03, Play, ClientBound => EntityAnimationSpec { entity_id: VarInt, animation: u8 // TODO: enumerate animation id }, AwardStatistics, 0x04, Play, ClientBound => AwardStatisticsSpec { statistics: CountedArray }, AcknowledgeBlockChange, 0x05, Play, ClientBound => AcknowledgeBlockChangeSpec { sequence_id: VarInt }, SetBlockDestroyStage, 0x06, Play, ClientBound => SetBlockDestroyStageSpec { entity_id: VarInt, location: IntPosition, destroy_stage: u8 }, BlockEntityData, 0x07, Play, ClientBound => BlockEntityDataSpec { location: IntPosition, block_entity_type: VarInt, nbt_data: NamedNbtTag }, BlockAction, 0x08, Play, ClientBound => BlockActionSpec { location: IntPosition, action_id: u8, action_parameter: u8, block_type: VarInt }, BlockUpdate, 0x09, Play, ClientBound => BlockUpdateSpec { location: IntPosition, block_id: VarInt }, BossBar, 0x0A, Play, ClientBound => BossBarSpec { uuid: UUID4, action: BossBarAction }, ChangeDifficulty, 0x0B, Play, ClientBound => ChangeDifficultySpec { difficulty: Difficulty, difficulty_locked: bool }, 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, root_index: VarInt }, CloseContainer, 0x0F, Play, ClientBound => CloseContainerSpec { window_id: u8 }, SetContainerContent, 0x10, Play, ClientBound => SetContainerContentSpec { window_id: u8, state_id: VarInt, slot_data: CountedArray, carried_item: Slot }, SetContainerProperty, 0x11, Play, ClientBound => SetContainerPropertySpec { window_id: u8, property: i16, value: i16 }, SetContainerSlot, 0x12, Play, ClientBound => SetContainerSlotSpec { window_id: u8, state_id: VarInt, slot_index: i16, slot_data: Slot }, SetCooldown, 0x13, Play, ClientBound => SetCooldownSpec { item_id: VarInt, cooldown_ticks: VarInt }, ChatSuggestions, 0x14, Play, ClientBound => ChatSuggestionsSpec { action: VarInt, entries: CountedArray }, PluginMessage, 0x15, Play, ClientBound => PluginMessageSpec { channel: String, data: RemainingBytes }, DeleteMessage, 0x16, Play, ClientBound => DeleteMessageSpec { signature: CountedArray }, DisconnectPlay, 0x17, Play, ClientBound => DisconnectPlaySpec { reason: Chat }, DisguisedChatMessage, 0x18, Play, ClientBound => DisguisedChatMessageSpec { message: Chat, chat_type: VarInt, chat_type_name: Chat, target_name: Option }, EntityEvent, 0x19, Play, ClientBound => EntityEventSpec { entity_id: i32, entity_status: u8 }, Explosion, 0x1A, Play, ClientBound => ExplosionSpec { position: Vec3, strength: f32, affected_blocks: CountedArray, VarInt>, player_motion: Vec3 }, UnloadChunk, 0x1B, Play, ClientBound => UnloadChunkSpec { chunk_x: i32, chunk_z: i32 }, GameEvent, 0x1C, Play, ClientBound => GameEventSpec { event: GameEventType, data: f32 }, OpenHorseScreen, 0x1D, Play, ClientBound => OpenHorseScreenSpec { window_id: u8, slot_count: VarInt, entity_id: i32 }, InitializeWorldBorder, 0x1E, Play, ClientBound => InitializeWorldBorderSpec { x: f64, z: f64, old_diameter: f64, new_diameter: f64, speed: VarLong, portal_teleport_boundary: VarInt, warning_blocks: VarInt, warning_time: VarInt }, KeepAliveClientBound, 0x1f, Play, ClientBound => KeepAliveClientBoundSpec { keep_alive_id: i64 }, ChunkDataAndUpdateLight, 0x20, Play, ClientBound => ChunkDataAndUpdateLightSpec { chunk_x: i32, chunk_z: i32, 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 enum PalettedContainer { SingularValue(VarInt), GlobalPalette(BlobArray), } impl Serialize for PalettedContainer { fn mc_serialize(&self, to: &mut S) -> SerializeResult { match self { PalettedContainer::SingularValue(value) => { to.serialize_byte(0)?; to.serialize_other(value)?; // Serialize 0 bit bitstorage to.serialize_other(&BitSet::empty())?; } PalettedContainer::GlobalPalette(data) => { to.serialize_byte(9)?; to.serialize_other(data)?; } } Ok(()) } } impl Deserialize for PalettedContainer { fn mc_deserialize(_data: &[u8]) -> DeserializeResult { Err(DeserializeErr::CannotUnderstandValue("Not implemented".into())) } } #[derive(Debug, PartialEq, Clone)] 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 = 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 = position * self.entry_size; let mask = (1u64 << self.entry_size) - 1; self.ensure_capacity(index); (self.data[array_index] >> array_shift) & mask } } impl Serialize for BlobArray { fn mc_serialize(&self, to: &mut S) -> SerializeResult { to.serialize_other(&VarInt::from(self.data.len() as i32))?; for elem in &self.data { to.serialize_other(elem)?; } Ok(()) } } impl Deserialize for BlobArray { fn mc_deserialize(_data: &[u8]) -> DeserializeResult { Err(DeserializeErr::CannotUnderstandValue("Not implemented".into())) } } 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, block_entity_type: VarInt, data: NamedNbtTag }); proto_byte_enum!(GameEventType, 0 :: NoRespawnBlockAvailable, 1 :: EndRaining, 2 :: BeginRaining, 3 :: ChangeGamemode, 4 :: WinGame, 5 :: DemoEvent, 6 :: ArrowHitPlayer, 7 :: RainLevelChange, 8 :: ThunderLevelChange, 9 :: PlayPufferfishStingSound, 10 :: PlayElderGuardianMobAppearance, 11 :: SetImmediateRespawn ); proto_struct!(CommandSuggestion { match_text: String, tooltip: Option }); proto_byte_enum!(Difficulty, 0 :: Peaceful, 1 :: Easy, 2 :: Normal, 3 :: Hard ); proto_byte_enum!(HandshakeNextState, 0x01 :: Status, 0x02 :: Login ); proto_struct!(LoginProperty { name: String, value: String, signature: Option }); proto_struct!(StatisticValue { category_id: StatisticCategory, statistic_id: VarInt, value: VarInt }); proto_varint_enum!(StatisticCategory, 0 :: Mined, 1 :: Crafted, 2 :: Used, 3 :: Broken, 4 :: PickedUp, 5 :: Dropped, 6 :: Killed, 7 :: KilledBy, 8 :: Custom ); proto_varint_enum!(BossBarAction, 0 :: Add(BossBarAddSpec), 1 :: Remove, 2 :: UpdateHealth(f32), 3 :: UpdateTitle(Chat), 4 :: UpdateStyle(BossBarUpdateStyleSpec), 5 :: UpdateFlags(BossBarFlags) ); proto_struct!(BossBarAddSpec { title: Chat, health: f32, color: BossBarColor, division: BossBarDivision, flags: BossBarFlags }); proto_struct!(BossBarUpdateStyleSpec { color: BossBarColor, dividers: BossBarDivision }); proto_varint_enum!(BossBarColor, 0 :: Pink, 1 :: Blue, 2 :: Red, 3 :: Green, 4 :: Yellow, 5 :: Purple, 6 :: White ); proto_varint_enum!(BossBarDivision, 0 :: NoDivision, 1 :: SixNotches, 2 :: TenNotches, 3 :: TwelveNotches, 4 :: TwentyNotches ); proto_byte_flag!(BossBarFlags, 0x01 :: is_darken_sky set_darken_sky, 0x02 :: is_dragon_bar set_dragon_bar, 0x03 :: is_create_fog set_create_fog );