diff --git a/src/item.rs b/src/item.rs index 19fa87c..7ed18d6 100644 --- a/src/item.rs +++ b/src/item.rs @@ -43,11 +43,32 @@ impl Serializable for Option { if id == -1 { return Ok(None); } + let count = buf.read_u8()? as isize; + let damage = buf.read_i16::()? as isize; + + let protocol_version = unsafe { protocol::CURRENT_PROTOCOL_VERSION }; + + let tag: Option = if protocol_version >= 47 { + Serializable::read_from(buf)? + } else { + // 1.7 uses a different slot data format described on https://wiki.vg/index.php?title=Slot_Data&diff=6056&oldid=4753 + let tag_size = buf.read_i16::()?; + if tag_size != -1 { + for _ in 0..tag_size { + let _ = buf.read_u8()?; + } + // TODO: decompress zlib NBT for 1.7 + None + } else { + None + } + }; + Ok(Some(Stack { id: id as isize, - count: buf.read_u8()? as isize, - damage: buf.read_i16::()? as isize, - tag: Serializable::read_from(buf)?, + count, + damage, + tag, })) } fn write_to(&self, buf: &mut W) -> Result<(), protocol::Error> { @@ -56,6 +77,7 @@ impl Serializable for Option { buf.write_i16::(val.id as i16)?; buf.write_u8(val.count as u8)?; buf.write_i16::(val.damage as i16)?; + // TODO: compress zlib NBT if 1.7 val.tag.write_to(buf)?; } None => buf.write_i16::(-1)?, diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index d78ac15..c94640c 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -37,7 +37,7 @@ use flate2::Compression; use std::time::{Instant, Duration}; use crate::shared::Position; -pub const SUPPORTED_PROTOCOLS: [i32; 8] = [340, 316, 315, 210, 109, 107, 74, 47]; +pub const SUPPORTED_PROTOCOLS: [i32; 9] = [340, 316, 315, 210, 109, 107, 74, 47, 5]; // TODO: switch to using thread_local storage?, see https://doc.rust-lang.org/std/macro.thread_local.html pub static mut CURRENT_PROTOCOL_VERSION: i32 = SUPPORTED_PROTOCOLS[0]; @@ -553,6 +553,16 @@ impl fmt::Debug for LenPrefixedBytes { } } +impl Lengthable for u8 { + fn into(self) -> usize { + self as usize + } + + fn from(u: usize) -> u8 { + u as u8 + } +} + impl Lengthable for i16 { fn into(self) -> usize { self as usize diff --git a/src/protocol/packet.rs b/src/protocol/packet.rs index 5006d13..4134491 100644 --- a/src/protocol/packet.rs +++ b/src/protocol/packet.rs @@ -66,6 +66,9 @@ state_packets!( field has_target: bool =, field target: Option = when(|p: &TabComplete_NoAssume| p.has_target), } + packet TabComplete_NoAssume_NoTarget { + field text: String =, + } /// ChatMessage is sent by the client when it sends a chat message or /// executes a command (prefixed by '/'). packet ChatMessage { @@ -75,6 +78,9 @@ state_packets!( packet ClientStatus { field action_id: VarInt =, } + packet ClientStatus_u8 { + field action_id: u8=, + } /// ClientSettings is sent by the client to update its current settings. packet ClientSettings { field locale: String =, @@ -99,6 +105,14 @@ state_packets!( field chat_colors: bool =, field displayed_skin_parts: u8 =, } + packet ClientSettings_u8_Handsfree_Difficulty { + field locale: String =, + field view_distance: u8 =, + field chat_mode: u8 =, + field chat_colors: bool =, + field difficulty: u8 =, + field displayed_skin_parts: u8 =, + } /// ConfirmTransactionServerbound is a reply to ConfirmTransaction. packet ConfirmTransactionServerbound { field id: u8 =, @@ -138,6 +152,10 @@ state_packets!( field channel: String =, field data: Vec =, } + packet PluginMessageServerbound_i16 { + field channel: String =, + field data: LenPrefixedBytes =, + } /// UseEntity is sent when the user interacts (right clicks) or attacks /// (left clicks) an entity. packet UseEntity { @@ -155,6 +173,10 @@ state_packets!( field target_y: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), field target_z: f32 = when(|p: &UseEntity_Handsfree| p.ty.0 == 2), } + packet UseEntity_Handsfree_i32 { + field target_id: i32 =, + field ty: u8 =, + } /// KeepAliveServerbound is sent by a client as a response to a /// KeepAliveClientbound. If the client doesn't reply the server /// may disconnect the client. @@ -164,6 +186,9 @@ state_packets!( packet KeepAliveServerbound_VarInt { field id: VarInt =, } + packet KeepAliveServerbound_i32 { + field id: i32 =, + } /// PlayerPosition is used to update the player's position. packet PlayerPosition { field x: f64 =, @@ -171,6 +196,13 @@ state_packets!( field z: f64 =, field on_ground: bool =, } + packet PlayerPosition_HeadY { + field x: f64 =, + field feet_y: f64 =, + field head_y: f64 =, + field z: f64 =, + field on_ground: bool =, + } /// PlayerPositionLook is a combination of PlayerPosition and /// PlayerLook. packet PlayerPositionLook { @@ -181,6 +213,15 @@ state_packets!( field pitch: f32 =, field on_ground: bool =, } + packet PlayerPositionLook_HeadY { + field x: f64 =, + field feet_y: f64 =, + field head_y: f64 =, + field z: f64 =, + field yaw: f32 =, + field pitch: f32 =, + field on_ground: bool =, + } /// PlayerLook is used to update the player's rotation. packet PlayerLook { field yaw: f32 =, @@ -229,12 +270,24 @@ state_packets!( field location: Position =, field face: u8 =, } + packet PlayerDigging_u8_u8y { + field status: u8 =, + field x: i32 =, + field y: u8 =, + field z: i32 =, + field face: u8 =, + } /// PlayerAction is sent when a player preforms various actions. packet PlayerAction { field entity_id: VarInt =, field action_id: VarInt =, field jump_boost: VarInt =, } + packet PlayerAction_i32 { + field entity_id: i32 =, + field action_id: i8 =, + field jump_boost: i32 =, + } /// SteerVehicle is sent by the client when steers or preforms an action /// on a vehicle. packet SteerVehicle { @@ -242,6 +295,12 @@ state_packets!( field forward: f32 =, field flags: u8 =, } + packet SteerVehicle_jump_unmount { + field sideways: f32 =, + field forward: f32 =, + field jump: bool =, + field unmount: bool =, + } /// CraftingBookData is sent when the player interacts with the crafting book. packet CraftingBookData { field action: VarInt =, @@ -282,6 +341,15 @@ state_packets!( field line3: String =, field line4: String =, } + packet SetSign_i16y { + field x: i32 =, + field y: i16 =, + field z: i32 =, + field line1: String =, + field line2: String =, + field line3: String =, + field line4: String =, + } /// ArmSwing is sent by the client when the player left clicks (to swing their /// arm). packet ArmSwing { @@ -290,6 +358,10 @@ state_packets!( packet ArmSwing_Handsfree { field empty: () =, } + packet ArmSwing_Handsfree_ID { + field entity_id: i32 =, + field animation: u8 =, + } /// SpectateTeleport is sent by clients in spectator mode to teleport to a player. packet SpectateTeleport { field target: UUID =, @@ -319,6 +391,16 @@ state_packets!( field cursor_y: u8 =, field cursor_z: u8 =, } + packet PlayerBlockPlacement_u8_Item_u8y { + field x: i32 =, + field y: u8 =, + field z: i32 =, + field face: u8 =, + field hand: Option =, + field cursor_x: u8 =, + field cursor_y: u8 =, + field cursor_z: u8 =, + } /// UseItem is sent when the client tries to use an item. packet UseItem { @@ -478,6 +560,14 @@ state_packets!( field location: Position =, field direction: u8 =, } + packet SpawnPainting_NoUUID_i32 { + field entity_id: VarInt =, + field title: String =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field direction: i32 =, + } /// SpawnPlayer is used to spawn a player when they are in range of the client. /// This packet alone isn't enough to display the player as the skin and username /// information is in the player information packet. @@ -512,6 +602,20 @@ state_packets!( field current_item: u16 =, field metadata: types::Metadata =, } + packet SpawnPlayer_i32_HeldItem_String { + field entity_id: VarInt =, + field uuid: String =, + field name: String =, + field properties: LenPrefixed =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field yaw: i8 =, + field pitch: i8 =, + field current_item: u16 =, + field metadata: types::Metadata =, + } + /// Animation is sent by the server to play an animation on a specific entity. packet Animation { field entity_id: VarInt =, @@ -528,6 +632,13 @@ state_packets!( field location: Position =, field stage: i8 =, } + packet BlockBreakAnimation_i32 { + field entity_id: VarInt =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field stage: i8 =, + } /// UpdateBlockEntity updates the nbt tag of a block entity in the /// world. packet UpdateBlockEntity { @@ -535,6 +646,14 @@ state_packets!( field action: u8 =, field nbt: Option =, } + packet UpdateBlockEntity_Data { + field x: i32 =, + field y: i16 =, + field z: i32 =, + field action: u8 =, + field data_length: i16 =, + field gzipped_nbt: Vec =, + } /// BlockAction triggers different actions depending on the target block. packet BlockAction { field location: Position =, @@ -542,11 +661,26 @@ state_packets!( field byte2: u8 =, field block_type: VarInt =, } + packet BlockAction_u16 { + field x: i32 =, + field y: u16 =, + field z: i32 =, + field byte1: u8 =, + field byte2: u8 =, + field block_type: VarInt =, + } /// BlockChange is used to update a single block on the client. - packet BlockChange { + packet BlockChange_VarInt { field location: Position =, field block_id: VarInt =, } + packet BlockChange_u8 { + field x: i32 =, + field y: u8 =, + field z: i32 =, + field block_id: VarInt =, + field block_metadata: u8 =, + } /// BossBar displays and/or changes a boss bar that is displayed on the /// top of the client's screen. This is normally used for bosses such as /// the ender dragon or the wither. @@ -578,12 +712,22 @@ state_packets!( /// 0 - Chat message, 1 - System message, 2 - Action bar message field position: u8 =, } + packet ServerMessage_NoPosition { + field message: format::Component =, + } /// MultiBlockChange is used to update a batch of blocks in a single packet. - packet MultiBlockChange { + packet MultiBlockChange_VarInt { field chunk_x: i32 =, field chunk_z: i32 =, field records: LenPrefixed =, } + packet MultiBlockChange_u16 { + field chunk_x: i32 =, + field chunk_z: i32 =, + field record_count: u16 =, + field data_size: i32 =, + field data: Vec =, + } /// ConfirmTransaction notifies the client whether a transaction was successful /// or failed (e.g. due to lag). packet ConfirmTransaction { @@ -606,6 +750,14 @@ state_packets!( field slot_count: u8 =, field entity_id: i32 = when(|p: &WindowOpen| p.ty == "EntityHorse"), } + packet WindowOpen_u8 { + field id: u8 =, + field ty: u8 =, + field title: format::Component =, + field slot_count: u8 =, + field use_provided_window_title: bool =, + field entity_id: i32 = when(|p: &WindowOpen_u8| p.ty == 11), + } /// WindowItems sets every item in a window. packet WindowItems { field id: u8 =, @@ -636,6 +788,10 @@ state_packets!( field channel: String =, field data: Vec =, } + packet PluginMessageClientbound_i16 { + field channel: String =, + field data: LenPrefixedBytes =, + } /// Plays a sound by name on the client packet NamedSoundEffect { field name: String =, @@ -711,6 +867,9 @@ state_packets!( packet KeepAliveClientbound_VarInt { field id: VarInt =, } + packet KeepAliveClientbound_i32 { + field id: i32 =, + } /// ChunkData sends or updates a single chunk on the client. If New is set /// then biome data should be sent too. packet ChunkData { @@ -735,11 +894,25 @@ state_packets!( field bitmask: u16 =, field data: LenPrefixedBytes =, } + packet ChunkData_17 { + field chunk_x: i32 =, + field chunk_z: i32 =, + field new: bool =, + field bitmask: u16 =, + field add_bitmask: u16 =, + field compressed_data: LenPrefixedBytes =, + } packet ChunkDataBulk { field skylight: bool =, field chunk_meta: LenPrefixed =, field chunk_data: Vec =, } + packet ChunkDataBulk_17 { + field chunk_column_count: u16 =, + field data_length: i32 =, + field skylight: bool =, + field chunk_data_and_meta: Vec =, + } /// Effect plays a sound effect or particle at the target location with the /// volume (of sounds) being relative to the player's position unless /// DisableRelative is set to true. @@ -749,6 +922,14 @@ state_packets!( field data: i32 =, field disable_relative: bool =, } + packet Effect_u8y { + field effect_id: i32 =, + field x: i32 =, + field y: u8 =, + field z: i32 =, + field data: i32 =, + field disable_relative: bool =, + } /// Particle spawns particles at the target location with the various /// modifiers. packet Particle { @@ -765,6 +946,17 @@ state_packets!( field data1: VarInt = when(|p: &Particle| p.particle_id == 36 || p.particle_id == 37 || p.particle_id == 38 || p.particle_id == 46), field data2: VarInt = when(|p: &Particle| p.particle_id == 36), } + packet Particle_Named { + field particle_id: String =, + field x: f32 =, + field y: f32 =, + field z: f32 =, + field offset_x: f32 =, + field offset_y: f32 =, + field offset_z: f32 =, + field speed: f32 =, + field count: i32 =, + } /// JoinGame is sent after completing the login process. This /// sets the initial state for the client. packet JoinGame_i32 { @@ -801,6 +993,14 @@ state_packets!( /// information it displays in F3 mode field reduced_debug_info: bool =, } + packet JoinGame_i8_NoDebug { + field entity_id: i32 =, + field gamemode: u8 =, + field dimension: i8 =, + field difficulty: u8 =, + field max_players: u8 =, + field level_type: String =, + } /// Maps updates a single map's contents packet Maps { field item_damage: VarInt =, @@ -823,6 +1023,10 @@ state_packets!( field z: Option = when(|p: &Maps_NoTracking| p.columns > 0), field data: Option> = when(|p: &Maps_NoTracking| p.columns > 0), } + packet Maps_NoTracking_Data { + field item_damage: VarInt =, + field data: LenPrefixedBytes =, + } /// EntityMove moves the entity with the id by the offsets provided. packet EntityMove_i16 { field entity_id: VarInt =, @@ -838,6 +1042,12 @@ state_packets!( field delta_z: i8 =, field on_ground: bool =, } + packet EntityMove_i8_i32_NoGround { + field entity_id: i32 =, + field delta_x: i8 =, + field delta_y: i8 =, + field delta_z: i8 =, + } /// EntityLookAndMove is a combination of EntityMove and EntityLook. packet EntityLookAndMove_i16 { field entity_id: VarInt =, @@ -857,17 +1067,33 @@ state_packets!( field pitch: i8 =, field on_ground: bool =, } + packet EntityLookAndMove_i8_i32_NoGround { + field entity_id: i32 =, + field delta_x: i8 =, + field delta_y: i8 =, + field delta_z: i8 =, + field yaw: i8 =, + field pitch: i8 =, + } /// EntityLook rotates the entity to the new angles provided. - packet EntityLook { + packet EntityLook_VarInt { field entity_id: VarInt =, field yaw: i8 =, field pitch: i8 =, field on_ground: bool =, } + packet EntityLook_i32_NoGround { + field entity_id: i32 =, + field yaw: i8 =, + field pitch: i8 =, + } /// Entity does nothing. It is a result of subclassing used in Minecraft. packet Entity { field entity_id: VarInt =, } + packet Entity_i32 { + field entity_id: i32 =, + } /// EntityUpdateNBT updates the entity named binary tag. packet EntityUpdateNBT { field entity_id: VarInt =, @@ -886,6 +1112,11 @@ state_packets!( packet SignEditorOpen { field location: Position =, } + packet SignEditorOpen_i32 { + field x: i32 =, + field y: i32 =, + field z: i32 =, + } /// CraftRecipeResponse is a response to CraftRecipeRequest, notifies the UI. packet CraftRecipeResponse { field window_id: u8 =, @@ -912,6 +1143,11 @@ state_packets!( packet PlayerInfo { field inner: packet::PlayerInfoData =, } + packet PlayerInfo_String { + field name: String =, + field online: bool =, + field ping: u16 =, + } /// TeleportPlayer is sent to change the player's position. The client is expected /// to reply to the server with the same positions as contained in this packet /// otherwise will reject future packets. @@ -937,6 +1173,12 @@ state_packets!( field entity_id: VarInt =, field location: Position =, } + packet EntityUsedBed_i32 { + field entity_id: i32 =, + field x: i32 =, + field y: u8 =, + field z: i32 =, + } packet UnlockRecipes { field action: VarInt =, field crafting_book_open: bool =, @@ -948,11 +1190,18 @@ state_packets!( packet EntityDestroy { field entity_ids: LenPrefixed =, } + packet EntityDestroy_u8 { + field entity_ids: LenPrefixed =, + } /// EntityRemoveEffect removes an effect from an entity. packet EntityRemoveEffect { field entity_id: VarInt =, field effect_id: i8 =, } + packet EntityRemoveEffect_i32 { + field entity_id: i32 =, + field effect_id: i8 =, + } /// ResourcePackSend causes the client to check its cache for the requested /// resource packet and download it if its missing. Once the resource pack /// is obtained the client will use it. @@ -972,6 +1221,10 @@ state_packets!( field entity_id: VarInt =, field head_yaw: i8 =, } + packet EntityHeadLook_i32 { + field entity_id: i32 =, + field head_yaw: i8 =, + } packet EntityStatus { field entity_id: i32 =, field entity_status: i8 =, @@ -1012,6 +1265,10 @@ state_packets!( field entity_id: VarInt =, field metadata: types::Metadata =, } + packet EntityMetadata_i32 { + field entity_id: i32 =, + field metadata: types::Metadata =, + } /// EntityAttach attaches to entities together, either by mounting or leashing. /// -1 can be used at the EntityID to deattach. packet EntityAttach { @@ -1031,6 +1288,12 @@ state_packets!( field velocity_y: i16 =, field velocity_z: i16 =, } + packet EntityVelocity_i32 { + field entity_id: i32 =, + field velocity_x: i16 =, + field velocity_y: i16 =, + field velocity_z: i16 =, + } /// EntityEquipment is sent to display an item on an entity, like a sword /// or armor. Slot 0 is the held item and slots 1 to 4 are boots, leggings /// chestplate and helmet respectively. @@ -1044,18 +1307,33 @@ state_packets!( field slot: u16 =, field item: Option =, } + packet EntityEquipment_u16_i32 { + field entity_id: i32 =, + field slot: u16 =, + field item: Option =, + } /// SetExperience updates the experience bar on the client. packet SetExperience { field experience_bar: f32 =, field level: VarInt =, field total_experience: VarInt =, } + packet SetExperience_i16 { + field experience_bar: f32 =, + field level: i16 =, + field total_experience: i16 =, + } /// UpdateHealth is sent by the server to update the player's health and food. packet UpdateHealth { field health: f32 =, field food: VarInt =, field food_saturation: f32 =, } + packet UpdateHealth_u16 { + field health: f32 =, + field food: u16 =, + field food_saturation: f32 =, + } /// ScoreboardObjective creates/updates a scoreboard objective. packet ScoreboardObjective { field name: String =, @@ -1063,6 +1341,11 @@ state_packets!( field value: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), field ty: String = when(|p: &ScoreboardObjective| p.mode == 0 || p.mode == 2), } + packet ScoreboardObjective_NoMode { + field name: String =, + field value: String =, + field ty: u8 =, + } /// SetPassengers mounts entities to an entity packet SetPassengers { field entity_id: VarInt =, @@ -1081,6 +1364,15 @@ state_packets!( field color: Option = when(|p: &Teams| p.mode == 0 || p.mode == 2), field players: Option> = when(|p: &Teams| p.mode == 0 || p.mode == 3 || p.mode == 4), } + packet Teams_NoVisColor { + field name: String =, + field mode: u8 =, + field display_name: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field prefix: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field suffix: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field flags: Option = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 2), + field players: Option> = when(|p: &Teams_NoVisColor| p.mode == 0 || p.mode == 3 || p.mode == 4), + } /// UpdateScore is used to update or remove an item from a scoreboard /// objective. packet UpdateScore { @@ -1089,11 +1381,22 @@ state_packets!( field object_name: String =, field value: Option = when(|p: &UpdateScore| p.action != 1), } + packet UpdateScore_i32 { + field name: String =, + field action: u8 =, + field object_name: String =, + field value: Option = when(|p: &UpdateScore_i32| p.action != 1), + } /// SpawnPosition is sent to change the player's current spawn point. Currently /// only used by the client for the compass. packet SpawnPosition { field location: Position =, } + packet SpawnPosition_i32 { + field x: i32 =, + field y: i32 =, + field z: i32 =, + } /// TimeUpdate is sent to sync the world's time to the client, the client /// will manually tick the time itself so this doesn't need to sent repeatedly /// but if the server or client has issues keeping up this can fall out of sync @@ -1136,6 +1439,15 @@ state_packets!( field line3: format::Component =, field line4: format::Component =, } + packet UpdateSign_u16 { + field x: i32 =, + field y: u16 =, + field z: i32 =, + field line1: format::Component =, + field line2: format::Component =, + field line3: format::Component =, + field line4: format::Component =, + } /// SoundEffect plays the named sound at the target location. packet SoundEffect { field name: VarInt =, @@ -1171,6 +1483,10 @@ state_packets!( field collected_entity_id: VarInt =, field collector_entity_id: VarInt =, } + packet CollectItem_nocount_i32 { + field collected_entity_id: i32 =, + field collector_entity_id: i32 =, + } /// EntityTeleport teleports the entity to the target location. This is /// sent if the entity moves further than EntityMove allows. packet EntityTeleport_f64 { @@ -1191,6 +1507,14 @@ state_packets!( field pitch: i8 =, field on_ground: bool =, } + packet EntityTeleport_i32_i32_NoGround { + field entity_id: i32 =, + field x: i32 =, + field y: i32 =, + field z: i32 =, + field yaw: i8 =, + field pitch: i8 =, + } packet Advancements { field reset_clear: bool =, field mapping: LenPrefixed =, @@ -1202,6 +1526,10 @@ state_packets!( field entity_id: VarInt =, field properties: LenPrefixed =, } + packet EntityProperties_i32 { + field entity_id: i32 =, + field properties: LenPrefixed =, + } /// EntityEffect applies a status effect to an entity for a given duration. packet EntityEffect { field entity_id: VarInt =, @@ -1210,6 +1538,12 @@ state_packets!( field duration: VarInt =, field hide_particles: bool =, } + packet EntityEffect_i32 { + field entity_id: i32 =, + field effect_id: i8 =, + field amplifier: i8 =, + field duration: i16 =, + } } } login Login { @@ -1231,6 +1565,10 @@ state_packets!( /// public key field verify_token: LenPrefixedBytes =, } + packet EncryptionResponse_i16 { + field shared_secret: LenPrefixedBytes =, + field verify_token: LenPrefixedBytes =, + } } clientbound Clientbound { /// LoginDisconnect is sent by the server if there was any issues @@ -1252,6 +1590,11 @@ state_packets!( /// correctly field verify_token: LenPrefixedBytes =, } + packet EncryptionRequest_i16 { + field server_id: String =, + field public_key: LenPrefixedBytes =, + field verify_token: LenPrefixedBytes =, + } /// LoginSuccess is sent by the server if the player successfully /// authenicates with the session servers (online mode) or straight /// after LoginStart (offline mode). @@ -1323,6 +1666,29 @@ state_packets!( } ); +#[derive(Debug, Default)] +pub struct SpawnProperty { + pub name: String, + pub value: String, + pub signature: String, +} + +impl Serializable for SpawnProperty { + fn read_from(buf: &mut R) -> Result { + Ok(SpawnProperty { + name: Serializable::read_from(buf)?, + value: Serializable::read_from(buf)?, + signature: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.name.write_to(buf)?; + self.value.write_to(buf)?; + self.signature.write_to(buf) + } +} + #[derive(Debug, Default)] pub struct Statistic { pub name: String, @@ -1625,6 +1991,30 @@ impl Serializable for EntityProperty { } } +#[derive(Debug, Default)] +pub struct EntityProperty_i16 { + pub key: String, + pub value: f64, + pub modifiers: LenPrefixed, +} + +impl Serializable for EntityProperty_i16 { + fn read_from(buf: &mut R) -> Result { + Ok(EntityProperty_i16 { + key: Serializable::read_from(buf)?, + value: Serializable::read_from(buf)?, + modifiers: Serializable::read_from(buf)?, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.key.write_to(buf)?; + self.value.write_to(buf)?; + self.modifiers.write_to(buf) + } +} + + #[derive(Debug, Default)] pub struct PropertyModifier { pub uuid: UUID, diff --git a/src/protocol/versions.rs b/src/protocol/versions.rs index 3e71b28..2d022fc 100644 --- a/src/protocol/versions.rs +++ b/src/protocol/versions.rs @@ -7,6 +7,7 @@ mod v1_9_2; mod v1_9; mod v15w39c; mod v1_8_9; +mod v1_7_10; pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: Direction, id: i32, to_internal: bool) -> i32 { match version { @@ -36,6 +37,9 @@ pub fn translate_internal_packet_id_for_version(version: i32, state: State, dir: // 1.8.9 - 1.8 47 => v1_8_9::translate_internal_packet_id(state, dir, id, to_internal), + // 1.7.10 - 1.7.6 + 5 => v1_7_10::translate_internal_packet_id(state, dir, id, to_internal), + _ => panic!("unsupported protocol version"), } } diff --git a/src/protocol/versions/v15w39c.rs b/src/protocol/versions/v15w39c.rs index 35847be..edf5c71 100644 --- a/src/protocol/versions/v15w39c.rs +++ b/src/protocol/versions/v15w39c.rs @@ -48,12 +48,12 @@ protocol_packet_ids!( 0x08 => BlockBreakAnimation 0x09 => UpdateBlockEntity 0x0a => BlockAction - 0x0b => BlockChange + 0x0b => BlockChange_VarInt 0x0c => BossBar 0x0d => ServerDifficulty 0x0e => TabCompleteReply 0x0f => ServerMessage - 0x10 => MultiBlockChange + 0x10 => MultiBlockChange_VarInt 0x11 => ConfirmTransaction 0x12 => WindowClose 0x13 => WindowOpen @@ -77,7 +77,7 @@ protocol_packet_ids!( 0x25 => Maps 0x26 => EntityMove_i8 0x27 => EntityLookAndMove_i8 - 0x28 => EntityLook + 0x28 => EntityLook_VarInt 0x29 => Entity 0x2a => SignEditorOpen 0x2b => PlayerAbilities diff --git a/src/protocol/versions/v1_10_2.rs b/src/protocol/versions/v1_10_2.rs index 0be2deb..e5dc3fd 100644 --- a/src/protocol/versions/v1_10_2.rs +++ b/src/protocol/versions/v1_10_2.rs @@ -51,12 +51,12 @@ protocol_packet_ids!( 0x08 => BlockBreakAnimation 0x09 => UpdateBlockEntity 0x0a => BlockAction - 0x0b => BlockChange + 0x0b => BlockChange_VarInt 0x0c => BossBar 0x0d => ServerDifficulty 0x0e => TabCompleteReply 0x0f => ServerMessage - 0x10 => MultiBlockChange + 0x10 => MultiBlockChange_VarInt 0x11 => ConfirmTransaction 0x12 => WindowClose 0x13 => WindowOpen @@ -79,7 +79,7 @@ protocol_packet_ids!( 0x24 => Maps 0x25 => EntityMove_i16 0x26 => EntityLookAndMove_i16 - 0x27 => EntityLook + 0x27 => EntityLook_VarInt 0x28 => Entity 0x29 => VehicleTeleport 0x2a => SignEditorOpen diff --git a/src/protocol/versions/v1_11_2.rs b/src/protocol/versions/v1_11_2.rs index fed44e4..1f09b13 100644 --- a/src/protocol/versions/v1_11_2.rs +++ b/src/protocol/versions/v1_11_2.rs @@ -51,12 +51,12 @@ protocol_packet_ids!( 0x08 => BlockBreakAnimation 0x09 => UpdateBlockEntity 0x0a => BlockAction - 0x0b => BlockChange + 0x0b => BlockChange_VarInt 0x0c => BossBar 0x0d => ServerDifficulty 0x0e => TabCompleteReply 0x0f => ServerMessage - 0x10 => MultiBlockChange + 0x10 => MultiBlockChange_VarInt 0x11 => ConfirmTransaction 0x12 => WindowClose 0x13 => WindowOpen @@ -79,7 +79,7 @@ protocol_packet_ids!( 0x24 => Maps 0x25 => EntityMove_i16 0x26 => EntityLookAndMove_i16 - 0x27 => EntityLook + 0x27 => EntityLook_VarInt 0x28 => Entity 0x29 => VehicleTeleport 0x2a => SignEditorOpen diff --git a/src/protocol/versions/v1_12_2.rs b/src/protocol/versions/v1_12_2.rs index b5765eb..c609790 100644 --- a/src/protocol/versions/v1_12_2.rs +++ b/src/protocol/versions/v1_12_2.rs @@ -54,12 +54,12 @@ protocol_packet_ids!( 0x08 => BlockBreakAnimation 0x09 => UpdateBlockEntity 0x0a => BlockAction - 0x0b => BlockChange + 0x0b => BlockChange_VarInt 0x0c => BossBar 0x0d => ServerDifficulty 0x0e => TabCompleteReply 0x0f => ServerMessage - 0x10 => MultiBlockChange + 0x10 => MultiBlockChange_VarInt 0x11 => ConfirmTransaction 0x12 => WindowClose 0x13 => WindowOpen @@ -83,7 +83,7 @@ protocol_packet_ids!( 0x25 => Entity 0x26 => EntityMove_i16 0x27 => EntityLookAndMove_i16 - 0x28 => EntityLook + 0x28 => EntityLook_VarInt 0x29 => VehicleTeleport 0x2a => SignEditorOpen 0x2b => CraftRecipeResponse diff --git a/src/protocol/versions/v1_7_10.rs b/src/protocol/versions/v1_7_10.rs new file mode 100644 index 0000000..5ca41d9 --- /dev/null +++ b/src/protocol/versions/v1_7_10.rs @@ -0,0 +1,127 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => KeepAliveServerbound_i32 + 0x01 => ChatMessage + 0x02 => UseEntity_Handsfree_i32 + 0x03 => Player + 0x04 => PlayerPosition_HeadY + 0x05 => PlayerLook + 0x06 => PlayerPositionLook_HeadY + 0x07 => PlayerDigging_u8_u8y + 0x08 => PlayerBlockPlacement_u8_Item_u8y + 0x09 => HeldItemChange + 0x0a => ArmSwing_Handsfree_ID + 0x0b => PlayerAction_i32 + 0x0c => SteerVehicle_jump_unmount + 0x0d => CloseWindow + 0x0e => ClickWindow_u8 + 0x0f => ConfirmTransactionServerbound + 0x10 => CreativeInventoryAction + 0x11 => EnchantItem + 0x12 => SetSign_i16y + 0x13 => ClientAbilities + 0x14 => TabComplete_NoAssume_NoTarget + 0x15 => ClientSettings_u8_Handsfree_Difficulty + 0x16 => ClientStatus_u8 + 0x17 => PluginMessageServerbound_i16 + } + clientbound Clientbound { + 0x00 => KeepAliveClientbound_i32 + 0x01 => JoinGame_i8_NoDebug + 0x02 => ServerMessage_NoPosition + 0x03 => TimeUpdate + 0x04 => EntityEquipment_u16_i32 + 0x05 => SpawnPosition_i32 + 0x06 => UpdateHealth_u16 + 0x07 => Respawn + 0x08 => TeleportPlayer_NoConfirm + 0x09 => SetCurrentHotbarSlot + 0x0a => EntityUsedBed_i32 + 0x0b => Animation + 0x0c => SpawnPlayer_i32_HeldItem_String + 0x0d => CollectItem_nocount_i32 + 0x0e => SpawnObject_i32_NoUUID + 0x0f => SpawnMob_u8_i32_NoUUID + 0x10 => SpawnPainting_NoUUID_i32 + 0x11 => SpawnExperienceOrb_i32 + 0x12 => EntityVelocity_i32 + 0x13 => EntityDestroy_u8 + 0x14 => Entity_i32 + 0x15 => EntityMove_i8_i32_NoGround + 0x16 => EntityLook_i32_NoGround + 0x17 => EntityLookAndMove_i8_i32_NoGround + 0x18 => EntityTeleport_i32_i32_NoGround + 0x19 => EntityHeadLook_i32 + 0x1a => EntityStatus + 0x1b => EntityAttach_leashed + 0x1c => EntityMetadata_i32 + 0x1d => EntityEffect_i32 + 0x1e => EntityRemoveEffect_i32 + 0x1f => SetExperience_i16 + 0x20 => EntityProperties_i32 + 0x21 => ChunkData_17 + 0x22 => MultiBlockChange_u16 + 0x23 => BlockChange_u8 + 0x24 => BlockAction_u16 + 0x25 => BlockBreakAnimation_i32 + 0x26 => ChunkDataBulk_17 + 0x27 => Explosion + 0x28 => Effect_u8y + 0x29 => NamedSoundEffect_u8_NoCategory + 0x2a => Particle_Named + 0x2b => ChangeGameState + 0x2c => SpawnGlobalEntity_i32 + 0x2d => WindowOpen_u8 + 0x2e => WindowClose + 0x2f => WindowSetSlot + 0x30 => WindowItems + 0x31 => WindowProperty + 0x32 => ConfirmTransaction + 0x33 => UpdateSign_u16 + 0x34 => Maps_NoTracking_Data + 0x35 => UpdateBlockEntity_Data + 0x36 => SignEditorOpen_i32 + 0x37 => Statistics + 0x38 => PlayerInfo_String + 0x39 => PlayerAbilities + 0x3a => TabCompleteReply + 0x3b => ScoreboardObjective_NoMode + 0x3c => UpdateScore_i32 + 0x3d => ScoreboardDisplay + 0x3e => Teams_NoVisColor + 0x3f => PluginMessageClientbound_i16 + 0x40 => Disconnect + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart + 0x01 => EncryptionResponse_i16 + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest_i16 + 0x02 => LoginSuccess + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); + + diff --git a/src/protocol/versions/v1_8_9.rs b/src/protocol/versions/v1_8_9.rs index 362b93c..b8e531e 100644 --- a/src/protocol/versions/v1_8_9.rs +++ b/src/protocol/versions/v1_8_9.rs @@ -58,7 +58,7 @@ protocol_packet_ids!( 0x13 => EntityDestroy 0x14 => Entity 0x15 => EntityMove_i8 - 0x16 => EntityLook + 0x16 => EntityLook_VarInt 0x17 => EntityLookAndMove_i8 0x18 => EntityTeleport_i32 0x19 => EntityHeadLook @@ -70,8 +70,8 @@ protocol_packet_ids!( 0x1f => SetExperience 0x20 => EntityProperties 0x21 => ChunkData_NoEntities_u16 - 0x22 => MultiBlockChange - 0x23 => BlockChange + 0x22 => MultiBlockChange_VarInt + 0x23 => BlockChange_VarInt 0x24 => BlockAction 0x25 => BlockBreakAnimation 0x26 => ChunkDataBulk diff --git a/src/protocol/versions/v1_9.rs b/src/protocol/versions/v1_9.rs index 07ccb44..2b598b3 100644 --- a/src/protocol/versions/v1_9.rs +++ b/src/protocol/versions/v1_9.rs @@ -51,12 +51,12 @@ protocol_packet_ids!( 0x08 => BlockBreakAnimation 0x09 => UpdateBlockEntity 0x0a => BlockAction - 0x0b => BlockChange + 0x0b => BlockChange_VarInt 0x0c => BossBar 0x0d => ServerDifficulty 0x0e => TabCompleteReply 0x0f => ServerMessage - 0x10 => MultiBlockChange + 0x10 => MultiBlockChange_VarInt 0x11 => ConfirmTransaction 0x12 => WindowClose 0x13 => WindowOpen @@ -79,7 +79,7 @@ protocol_packet_ids!( 0x24 => Maps 0x25 => EntityMove_i16 0x26 => EntityLookAndMove_i16 - 0x27 => EntityLook + 0x27 => EntityLook_VarInt 0x28 => Entity 0x29 => VehicleTeleport 0x2a => SignEditorOpen diff --git a/src/protocol/versions/v1_9_2.rs b/src/protocol/versions/v1_9_2.rs index 524f42f..6fc799d 100644 --- a/src/protocol/versions/v1_9_2.rs +++ b/src/protocol/versions/v1_9_2.rs @@ -51,12 +51,12 @@ protocol_packet_ids!( 0x08 => BlockBreakAnimation 0x09 => UpdateBlockEntity 0x0a => BlockAction - 0x0b => BlockChange + 0x0b => BlockChange_VarInt 0x0c => BossBar 0x0d => ServerDifficulty 0x0e => TabCompleteReply 0x0f => ServerMessage - 0x10 => MultiBlockChange + 0x10 => MultiBlockChange_VarInt 0x11 => ConfirmTransaction 0x12 => WindowClose 0x13 => WindowOpen @@ -79,7 +79,7 @@ protocol_packet_ids!( 0x24 => Maps 0x25 => EntityMove_i16 0x26 => EntityLookAndMove_i16 - 0x27 => EntityLook + 0x27 => EntityLook_VarInt 0x28 => Entity 0x29 => VehicleTeleport 0x2a => SignEditorOpen diff --git a/src/server/mod.rs b/src/server/mod.rs index ca147e8..0aacee0 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -120,14 +120,23 @@ impl Server { username: profile.username.clone(), })?; - let packet; + use std::rc::Rc; + let (server_id, public_key, verify_token); loop { match conn.read_packet()? { protocol::packet::Packet::SetInitialCompression(val) => { conn.set_compresssion(val.threshold.0); }, protocol::packet::Packet::EncryptionRequest(val) => { - packet = val; + server_id = Rc::new(val.server_id); + public_key = Rc::new(val.public_key.data); + verify_token = Rc::new(val.verify_token.data); + break; + }, + protocol::packet::Packet::EncryptionRequest_i16(val) => { + server_id = Rc::new(val.server_id); + public_key = Rc::new(val.public_key.data); + verify_token = Rc::new(val.verify_token.data); break; }, protocol::packet::Packet::LoginSuccess(val) => { @@ -145,25 +154,26 @@ impl Server { }; } - println!("packet.public_key.data = {:?}", &packet.public_key.data); let mut shared = [0; 16]; // TODO: is this cryptographically secure enough? rand::thread_rng().fill(&mut shared); - println!("shared ({:} bytes) = {:?}", shared.len(), &shared); - println!("packet.verify_token.data = {:?}", &packet.verify_token.data); + let shared_e = rsa_public_encrypt_pkcs1::encrypt(&public_key, &shared).unwrap(); + let token_e = rsa_public_encrypt_pkcs1::encrypt(&public_key, &verify_token).unwrap(); - let shared_e = rsa_public_encrypt_pkcs1::encrypt(&packet.public_key.data, &shared).unwrap(); - let token_e = rsa_public_encrypt_pkcs1::encrypt(&packet.public_key.data, &packet.verify_token.data).unwrap(); - println!("new shared_e({:}) = {:?}", shared_e.len(), &shared_e); - println!("new token_e({:}) = {:?}", token_e.len(), &token_e); + profile.join_server(&server_id, &shared, &public_key)?; - profile.join_server(&packet.server_id, &shared, &packet.public_key.data)?; - - conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse { - shared_secret: protocol::LenPrefixedBytes::new(shared_e), - verify_token: protocol::LenPrefixedBytes::new(token_e), - })?; + if protocol_version >= 47 { + conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse { + shared_secret: protocol::LenPrefixedBytes::new(shared_e), + verify_token: protocol::LenPrefixedBytes::new(token_e), + })?; + } else { + conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse_i16 { + shared_secret: protocol::LenPrefixedBytes::new(shared_e), + verify_token: protocol::LenPrefixedBytes::new(token_e), + })?; + } let mut read = conn.clone(); let mut write = conn.clone(); @@ -377,36 +387,51 @@ impl Server { self pck { JoinGame_i32 => on_game_join_i32, JoinGame_i8 => on_game_join_i8, + JoinGame_i8_NoDebug => on_game_join_i8_nodebug, Respawn => on_respawn, KeepAliveClientbound_i64 => on_keep_alive_i64, KeepAliveClientbound_VarInt => on_keep_alive_varint, + KeepAliveClientbound_i32 => on_keep_alive_i32, ChunkData => on_chunk_data, ChunkData_NoEntities => on_chunk_data_no_entities, ChunkData_NoEntities_u16 => on_chunk_data_no_entities_u16, + ChunkData_17 => on_chunk_data_17, ChunkDataBulk => on_chunk_data_bulk, + ChunkDataBulk_17 => on_chunk_data_bulk_17, ChunkUnload => on_chunk_unload, - BlockChange => on_block_change, - MultiBlockChange => on_multi_block_change, + BlockChange_VarInt => on_block_change_varint, + BlockChange_u8 => on_block_change_u8, + MultiBlockChange_VarInt => on_multi_block_change_varint, + MultiBlockChange_u16 => on_multi_block_change_u16, TeleportPlayer_WithConfirm => on_teleport_player_withconfirm, TeleportPlayer_NoConfirm => on_teleport_player_noconfirm, TimeUpdate => on_time_update, ChangeGameState => on_game_state_change, UpdateBlockEntity => on_block_entity_update, + UpdateBlockEntity_Data => on_block_entity_update_data, UpdateSign => on_sign_update, + UpdateSign_u16 => on_sign_update_u16, PlayerInfo => on_player_info, + PlayerInfo_String => on_player_info_string, Disconnect => on_disconnect, // Entities EntityDestroy => on_entity_destroy, + EntityDestroy_u8 => on_entity_destroy_u8, SpawnPlayer_f64 => on_player_spawn_f64, SpawnPlayer_i32 => on_player_spawn_i32, SpawnPlayer_i32_HeldItem => on_player_spawn_i32_helditem, + SpawnPlayer_i32_HeldItem_String => on_player_spawn_i32_helditem_string, EntityTeleport_f64 => on_entity_teleport_f64, EntityTeleport_i32 => on_entity_teleport_i32, + EntityTeleport_i32_i32_NoGround => on_entity_teleport_i32_i32_noground, EntityMove_i16 => on_entity_move_i16, EntityMove_i8 => on_entity_move_i8, - EntityLook => on_entity_look, + EntityMove_i8_i32_NoGround => on_entity_move_i8_i32_noground, + EntityLook_VarInt => on_entity_look_varint, + EntityLook_i32_NoGround => on_entity_look_i32_noground, EntityLookAndMove_i16 => on_entity_look_and_move_i16, EntityLookAndMove_i8 => on_entity_look_and_move_i8, + EntityLookAndMove_i8_i32_NoGround => on_entity_look_and_move_i8_i32_noground, } }, Err(err) => panic!("Err: {:?}", err), @@ -503,15 +528,28 @@ impl Server { // Sync our position to the server // Use the smaller packets when possible - let packet = packet::play::serverbound::PlayerPositionLook { - x: position.position.x, - y: position.position.y, - z: position.position.z, - yaw: -(rotation.yaw as f32) * (180.0 / PI), - pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0, - on_ground, - }; - self.write_packet(packet); + if self.protocol_version >= 47 { + let packet = packet::play::serverbound::PlayerPositionLook { + x: position.position.x, + y: position.position.y, + z: position.position.z, + yaw: -(rotation.yaw as f32) * (180.0 / PI), + pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0, + on_ground, + }; + self.write_packet(packet); + } else { + let packet = packet::play::serverbound::PlayerPositionLook_HeadY { + x: position.position.x, + feet_y: position.position.y - 1.62, + head_y: position.position.y, + z: position.position.z, + yaw: -(rotation.yaw as f32) * (180.0 / PI), + pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0, + on_ground, + }; + self.write_packet(packet); + } } } @@ -561,7 +599,7 @@ impl Server { cursor_y: (at.y * 16.0) as u8, cursor_z: (at.z * 16.0) as u8, }); - } else { + } else if self.protocol_version >= 47 { self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8_Item { location: pos, face: match face { @@ -578,6 +616,25 @@ impl Server { cursor_y: (at.y * 16.0) as u8, cursor_z: (at.z * 16.0) as u8, }); + } else { + self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8_Item_u8y { + x: pos.x, + y: pos.y as u8, + z: pos.x, + face: match face { + Direction::Down => 0, + Direction::Up => 1, + Direction::North => 2, + Direction::South => 3, + Direction::West => 4, + Direction::East => 5, + _ => unreachable!(), + }, + hand: None, + cursor_x: (at.x * 16.0) as u8, + cursor_y: (at.y * 16.0) as u8, + cursor_z: (at.z * 16.0) as u8, + }); } } } @@ -599,6 +656,12 @@ impl Server { }); } + fn on_keep_alive_i32(&mut self, keep_alive: packet::play::clientbound::KeepAliveClientbound_i32) { + self.write_packet(packet::play::serverbound::KeepAliveServerbound_i32 { + id: keep_alive.id, + }); + } + fn on_game_join_i32(&mut self, join: packet::play::clientbound::JoinGame_i32) { self.on_game_join(join.gamemode, join.entity_id) } @@ -607,6 +670,11 @@ impl Server { self.on_game_join(join.gamemode, join.entity_id) } + fn on_game_join_i8_nodebug(&mut self, join: packet::play::clientbound::JoinGame_i8_NoDebug) { + self.on_game_join(join.gamemode, join.entity_id) + } + + fn on_game_join(&mut self, gamemode: u8, entity_id: i32) { let gamemode = Gamemode::from_int((gamemode & 0x7) as i32); let player = entity::player::create_local(&mut self.entities); @@ -622,9 +690,14 @@ impl Server { self.player = Some(player); // Let the server know who we are - self.write_packet(plugin_messages::Brand { - brand: "Steven".into(), - }.as_message()); + let brand = plugin_messages::Brand { + brand: "Steven".into(), + }; + if self.protocol_version >= 47 { + self.write_packet(brand.as_message()); + } else { + self.write_packet(brand.as_message17()); + } } fn on_respawn(&mut self, respawn: packet::play::clientbound::Respawn) { @@ -672,6 +745,14 @@ impl Server { } } + fn on_entity_destroy_u8(&mut self, entity_destroy: packet::play::clientbound::EntityDestroy_u8) { + for id in entity_destroy.entity_ids.data { + if let Some(entity) = self.entity_map.remove(&id) { + self.entities.remove_entity(entity); + } + } + } + fn on_entity_teleport_f64(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_f64) { self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x, entity_telport.y, entity_telport.z, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground) } @@ -680,6 +761,12 @@ impl Server { self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground) } + fn on_entity_teleport_i32_i32_noground(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_i32_i32_NoGround) { + let on_ground = true; // TODO: how is this supposed to be set? (for 1.7) + self.on_entity_teleport(entity_telport.entity_id, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, on_ground) + } + + fn on_entity_teleport(&mut self, entity_id: i32, x: f64, y: f64, z: f64, yaw: f64, pitch: f64, _on_ground: bool) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.get(&entity_id) { @@ -701,6 +788,11 @@ impl Server { self.on_entity_move(m.entity_id.0, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64) } + fn on_entity_move_i8_i32_noground(&mut self, m: packet::play::clientbound::EntityMove_i8_i32_NoGround) { + self.on_entity_move(m.entity_id, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64) + } + + fn on_entity_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64) { if let Some(entity) = self.entity_map.get(&entity_id) { let position = self.entities.get_component_mut(*entity, self.target_position).unwrap(); @@ -710,15 +802,23 @@ impl Server { } } - fn on_entity_look(&mut self, look: packet::play::clientbound::EntityLook) { + fn on_entity_look(&mut self, entity_id: i32, yaw: f64, pitch: f64) { use std::f64::consts::PI; - if let Some(entity) = self.entity_map.get(&look.entity_id.0) { + if let Some(entity) = self.entity_map.get(&entity_id) { let rotation = self.entities.get_component_mut(*entity, self.target_rotation).unwrap(); - rotation.yaw = -((look.yaw as f64) / 256.0) * PI * 2.0; - rotation.pitch = -((look.pitch as f64) / 256.0) * PI * 2.0; + rotation.yaw = -(yaw / 256.0) * PI * 2.0; + rotation.pitch = -(pitch / 256.0) * PI * 2.0; } } + fn on_entity_look_varint(&mut self, look: packet::play::clientbound::EntityLook_VarInt) { + self.on_entity_look(look.entity_id.0, look.yaw as f64, look.pitch as f64) + } + + fn on_entity_look_i32_noground(&mut self, look: packet::play::clientbound::EntityLook_i32_NoGround) { + self.on_entity_look(look.entity_id, look.yaw as f64, look.pitch as f64) + } + fn on_entity_look_and_move_i16(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i16) { self.on_entity_look_and_move(lookmove.entity_id.0, lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64, @@ -731,6 +831,12 @@ impl Server { lookmove.yaw as f64, lookmove.pitch as f64) } + fn on_entity_look_and_move_i8_i32_noground(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i8_i32_NoGround) { + self.on_entity_look_and_move(lookmove.entity_id, + lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64, + lookmove.yaw as f64, lookmove.pitch as f64) + } + fn on_entity_look_and_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64, yaw: f64, pitch: f64) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.get(&entity_id) { @@ -756,6 +862,10 @@ impl Server { self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64) } + fn on_player_spawn_i32_helditem_string(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem_String) { + self.on_player_spawn(spawn.entity_id.0, protocol::UUID::from_str(&spawn.uuid), spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64) + } + fn on_player_spawn(&mut self, entity_id: i32, uuid: protocol::UUID, x: f64, y: f64, z: f64, pitch: f64, yaw: f64) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.remove(&entity_id) { @@ -855,6 +965,10 @@ impl Server { } } + fn on_block_entity_update_data(&mut self, _block_update: packet::play::clientbound::UpdateBlockEntity_Data) { + // TODO: handle UpdateBlockEntity_Data for 1.7, decompress gzipped_nbt + } + fn on_sign_update(&mut self, mut update_sign: packet::play::clientbound::UpdateSign) { use crate::format; format::convert_legacy(&mut update_sign.line1); @@ -870,6 +984,26 @@ impl Server { )); } + fn on_sign_update_u16(&mut self, mut update_sign: packet::play::clientbound::UpdateSign_u16) { + use crate::format; + format::convert_legacy(&mut update_sign.line1); + format::convert_legacy(&mut update_sign.line2); + format::convert_legacy(&mut update_sign.line3); + format::convert_legacy(&mut update_sign.line4); + self.world.add_block_entity_action(world::BlockEntityAction::UpdateSignText( + Position::new(update_sign.x, update_sign.y as i32, update_sign.z), + update_sign.line1, + update_sign.line2, + update_sign.line3, + update_sign.line4, + )); + } + + + fn on_player_info_string(&mut self, _player_info: packet::play::clientbound::PlayerInfo_String) { + // TODO: support PlayerInfo_String for 1.7 + } + fn on_player_info(&mut self, player_info: packet::play::clientbound::PlayerInfo) { use crate::protocol::packet::PlayerDetail::*; use base64; @@ -1003,23 +1137,39 @@ impl Server { self.world.load_chunks18(chunk_data.new, skylight, &chunk_meta, chunk_data.data.data).unwrap(); } + fn on_chunk_data_17(&mut self, chunk_data: packet::play::clientbound::ChunkData_17) { + self.world.load_chunk17(chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, chunk_data.bitmask, chunk_data.add_bitmask, chunk_data.compressed_data.data).unwrap(); + } + fn on_chunk_data_bulk(&mut self, bulk: packet::play::clientbound::ChunkDataBulk) { let new = true; self.world.load_chunks18(new, bulk.skylight, &bulk.chunk_meta.data, bulk.chunk_data.to_vec()).unwrap(); } + fn on_chunk_data_bulk_17(&mut self, bulk: packet::play::clientbound::ChunkDataBulk_17) { + self.world.load_chunks17(bulk.chunk_column_count, bulk.data_length, bulk.skylight, &bulk.chunk_data_and_meta).unwrap(); + } + fn on_chunk_unload(&mut self, chunk_unload: packet::play::clientbound::ChunkUnload) { self.world.unload_chunk(chunk_unload.x, chunk_unload.z, &mut self.entities); } - fn on_block_change(&mut self, block_change: packet::play::clientbound::BlockChange) { - self.world.set_block( - block_change.location, - block::Block::by_vanilla_id(block_change.block_id.0 as usize) + fn on_block_change(&mut self, location: Position, id: i32) { + self.world.set_block(location, block::Block::by_vanilla_id(id as usize)) + } + + fn on_block_change_varint(&mut self, block_change: packet::play::clientbound::BlockChange_VarInt) { + self.on_block_change(block_change.location, block_change.block_id.0) + } + + fn on_block_change_u8(&mut self, block_change: packet::play::clientbound::BlockChange_u8) { + self.on_block_change( + crate::shared::Position::new(block_change.x, block_change.y as i32, block_change.z), + (block_change.block_id.0 << 4) | (block_change.block_metadata as i32) ); } - fn on_multi_block_change(&mut self, block_change: packet::play::clientbound::MultiBlockChange) { + fn on_multi_block_change_varint(&mut self, block_change: packet::play::clientbound::MultiBlockChange_VarInt) { let ox = block_change.chunk_x << 4; let oz = block_change.chunk_z << 4; for record in block_change.records.data { @@ -1033,6 +1183,30 @@ impl Server { ); } } + + fn on_multi_block_change_u16(&mut self, block_change: packet::play::clientbound::MultiBlockChange_u16) { + let ox = block_change.chunk_x << 4; + let oz = block_change.chunk_z << 4; + + let mut data = std::io::Cursor::new(block_change.data); + + for _ in 0 .. block_change.record_count { + use byteorder::{BigEndian, ReadBytesExt}; + + let record = data.read_u32::().unwrap(); + + let id = record & 0x0000_ffff; + let y = ((record & 0x00ff_0000) >> 16) as i32; + let z = oz + ((record & 0x0f00_0000) >> 24) as i32; + let x = ox + ((record & 0xf000_0000) >> 28) as i32; + + self.world.set_block( + Position::new(x, y, z), + block::Block::by_vanilla_id(id as usize) + ); + } + } + } #[derive(Debug, Clone, Copy)] diff --git a/src/server/plugin_messages.rs b/src/server/plugin_messages.rs index ecbfa08..c0421ab 100644 --- a/src/server/plugin_messages.rs +++ b/src/server/plugin_messages.rs @@ -1,6 +1,7 @@ use crate::protocol::Serializable; use crate::protocol::packet::play::serverbound::PluginMessageServerbound; +use crate::protocol::packet::play::serverbound::PluginMessageServerbound_i16; pub struct Brand { pub brand: String, @@ -15,4 +16,15 @@ impl Brand { data, } } + + // TODO: cleanup this duplication for 1.7, return either message dynamically + pub fn as_message17(self) -> PluginMessageServerbound_i16 { + let mut data = vec![]; + Serializable::write_to(&self.brand, &mut data).unwrap(); + PluginMessageServerbound_i16 { + channel: "MC|Brand".into(), + data: crate::protocol::LenPrefixedBytes::::new(data), + } + } + } diff --git a/src/world/mod.rs b/src/world/mod.rs index 537392c..9ded72a 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -28,6 +28,8 @@ use crate::chunk_builder; use crate::ecs; use crate::entity::block_entity; use crate::format; +use flate2::read::ZlibDecoder; +use std::io::Read; pub mod biome; mod storage; @@ -558,6 +560,24 @@ impl World { Ok(()) } + fn dirty_chunks_by_bitmask(&mut self, x: i32, z: i32, mask: u16) { + for i in 0 .. 16 { + if mask & (1 << i) == 0 { + continue; + } + for pos in [ + (-1, 0, 0), (1, 0, 0), + (0, -1, 0), (0, 1, 0), + (0, 0, -1), (0, 0, 1)].into_iter() { + self.flag_section_dirty(x + pos.0, i as i32 + pos.1, z + pos.2); + } + self.update_range( + (x<<4) - 1, (i<<4) - 1, (z<<4) - 1, + (x<<4) + 17, (i<<4) + 17, (z<<4) + 17 + ); + } + } + pub fn load_chunk18(&mut self, x: i32, z: i32, new: bool, _skylight: bool, mask: u16, data: &mut std::io::Cursor>) -> Result<(), protocol::Error> { use std::io::Read; use byteorder::ReadBytesExt; @@ -635,21 +655,175 @@ impl World { chunk.calculate_heightmap(); } - for i in 0 .. 16 { - if mask & (1 << i) == 0 { - continue; - } - for pos in [ - (-1, 0, 0), (1, 0, 0), - (0, -1, 0), (0, 1, 0), - (0, 0, -1), (0, 0, 1)].into_iter() { - self.flag_section_dirty(x + pos.0, i as i32 + pos.1, z + pos.2); - } - self.update_range( - (x<<4) - 1, (i<<4) - 1, (z<<4) - 1, - (x<<4) + 17, (i<<4) + 17, (z<<4) + 17 - ); + self.dirty_chunks_by_bitmask(x, z, mask); + Ok(()) + } + + pub fn load_chunks17(&mut self, chunk_column_count: u16, data_length: i32, skylight: bool, data: &[u8]) -> Result<(), protocol::Error> { + let compressed_chunk_data = &data[0..data_length as usize]; + let metadata = &data[data_length as usize..]; + + let mut zlib = ZlibDecoder::new(std::io::Cursor::new(compressed_chunk_data.to_vec())); + let mut chunk_data = Vec::new(); + zlib.read_to_end(&mut chunk_data)?; + + let mut chunk_data = std::io::Cursor::new(chunk_data); + + // Chunk metadata + let mut metadata = std::io::Cursor::new(metadata); + for _i in 0..chunk_column_count { + use byteorder::ReadBytesExt; + + let x = metadata.read_i32::()?; + let z = metadata.read_i32::()?; + let mask = metadata.read_u16::()?; + let mask_add = metadata.read_u16::()?; + + let new = true; + + self.load_uncompressed_chunk17(x, z, new, skylight, mask, mask_add, &mut chunk_data)?; } + + Ok(()) + } + + pub fn load_chunk17(&mut self, x: i32, z: i32, new: bool, mask: u16, mask_add: u16, compressed_data: Vec) -> Result<(), protocol::Error> { + let mut zlib = ZlibDecoder::new(std::io::Cursor::new(compressed_data.to_vec())); + let mut data = Vec::new(); + zlib.read_to_end(&mut data)?; + + let skylight = true; + self.load_uncompressed_chunk17(x, z, new, skylight, mask, mask_add, &mut std::io::Cursor::new(data)) + } + + fn load_uncompressed_chunk17(&mut self, x: i32, z: i32, new: bool, skylight: bool, mask: u16, mask_add: u16, data: &mut std::io::Cursor>) -> Result<(), protocol::Error> { + use std::io::Read; + use crate::types::nibble; + + let cpos = CPos(x, z); + { + let chunk = if new { + self.chunks.insert(cpos, Chunk::new(cpos)); + self.chunks.get_mut(&cpos).unwrap() + } else { + if !self.chunks.contains_key(&cpos) { + return Ok(()); + } + self.chunks.get_mut(&cpos).unwrap() + }; + + // Block type array - whole byte per block + let mut block_types = [[0u8; 4096]; 16]; + for i in 0 .. 16 { + if chunk.sections[i].is_none() { + let mut fill_sky = chunk.sections.iter() + .skip(i) + .all(|v| v.is_none()); + fill_sky &= (mask & !((1 << i) | ((1 << i) - 1))) == 0; + if !fill_sky || mask & (1 << i) != 0 { + chunk.sections[i] = Some(Section::new(i as u8, fill_sky)); + } + } + if mask & (1 << i) == 0 { + continue; + } + let section = chunk.sections[i as usize].as_mut().unwrap(); + section.dirty = true; + + data.read_exact(&mut block_types[i])?; + } + + // Block metadata array - half byte per block + let mut block_meta: [nibble::Array; 16] = [ + // TODO: cleanup this initialization + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + ]; + + for i in 0 .. 16 { + if mask & (1 << i) == 0 { + continue; + } + + data.read_exact(&mut block_meta[i].data)?; + } + + // Block light array - half byte per block + for i in 0 .. 16 { + if mask & (1 << i) == 0 { + continue; + } + let section = chunk.sections[i as usize].as_mut().unwrap(); + + data.read_exact(&mut section.block_light.data)?; + } + + // Sky light array - half byte per block - only if 'skylight' is true + if skylight { + for i in 0 .. 16 { + if mask & (1 << i) == 0 { + continue; + } + let section = chunk.sections[i as usize].as_mut().unwrap(); + + data.read_exact(&mut section.sky_light.data)?; + } + } + + // Add array - half byte per block - uses secondary bitmask + let mut block_add: [nibble::Array; 16] = [ + // TODO: cleanup this initialization + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), nibble::Array::new(16 * 16 * 16), + ]; + + for i in 0 .. 16 { + if mask_add & (1 << i) == 0 { + continue; + } + data.read_exact(&mut block_add[i].data)?; + } + + // Now that we have the block types, metadata, and add, combine to initialize the blocks + for i in 0 .. 16 { + if mask & (1 << i) == 0 { + continue; + } + + let section = chunk.sections[i as usize].as_mut().unwrap(); + + for bi in 0 .. 4096 { + let id = ((block_add[i].get(bi) as u16) << 12) | ((block_types[i][bi] as u16) << 4) | (block_meta[i].get(bi) as u16); + section.blocks.set(bi, block::Block::by_vanilla_id(id as usize)); + + // Spawn block entities + let b = section.blocks.get(bi); + if block_entity::BlockEntityType::get_block_entity(b).is_some() { + let pos = Position::new( + (bi & 0xF) as i32, + (bi >> 8) as i32, + ((bi >> 4) & 0xF) as i32 + ) + (chunk.position.0 << 4, (i << 4) as i32, chunk.position.1 << 4); + if chunk.block_entities.contains_key(&pos) { + self.block_entity_actions.push_back(BlockEntityAction::Remove(pos)) + } + self.block_entity_actions.push_back(BlockEntityAction::Create(pos)) + } + } + } + + if new { + data.read_exact(&mut chunk.biomes)?; + } + + chunk.calculate_heightmap(); + } + + self.dirty_chunks_by_bitmask(x, z, mask); Ok(()) } @@ -733,21 +907,7 @@ impl World { chunk.calculate_heightmap(); } - for i in 0 .. 16 { - if mask & (1 << i) == 0 { - continue; - } - for pos in [ - (-1, 0, 0), (1, 0, 0), - (0, -1, 0), (0, 1, 0), - (0, 0, -1), (0, 0, 1)].into_iter() { - self.flag_section_dirty(x + pos.0, i as i32 + pos.1, z + pos.2); - } - self.update_range( - (x<<4) - 1, (i<<4) - 1, (z<<4) - 1, - (x<<4) + 17, (i<<4) + 17, (z<<4) + 17 - ); - } + self.dirty_chunks_by_bitmask(x, z, mask); Ok(()) }