From 5ef9648362b204e27d566f475ce7e57f49c8c19b Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Tue, 18 Aug 2026 22:19:13 +0200 Subject: [PATCH] fix: ci --- Cargo.lock | 1 + crates/pumpkin-data/src/generated/registry.rs | 5 +- crates/pumpkin-protocol/Cargo.toml | 1 + .../java/client/login/encryption_request.rs | 4 +- .../src/java/client/login/login_success.rs | 6 +- .../src/java/client/play/disconnect.rs | 9 +- .../java/client/play/entity_position_sync.rs | 38 ++++--- .../src/java/client/play/login.rs | 99 +++++++++++++++++++ .../src/java/client/play/mod.rs | 3 + .../java/client/play/player_info_update.rs | 8 +- .../src/java/client/play/player_position.rs | 9 +- .../src/java/client/play/respawn.rs | 23 +++++ .../src/java/client/play/set_held_item.rs | 4 +- .../src/java/client/play/teleport_entity.rs | 20 ++-- .../src/java/client/play/update_tags.rs | 53 ++++++++++ .../java/server/login/encryption_response.rs | 25 +++-- .../src/java/server/login/login_start.rs | 13 +++ .../src/entity/decoration/item_frame.rs | 44 +++++---- crates/pumpkin/src/entity/item.rs | 29 +++--- crates/pumpkin/src/entity/mod.rs | 7 +- crates/pumpkin/src/net/java/mod.rs | 26 +++-- crates/pumpkin/src/net/java/pending.rs | 24 ++++- .../pumpkin/src/server/tick_rate_manager.rs | 22 +++-- crates/pumpkin/src/world/mod.rs | 65 ++++++++++-- tools/pumpkin-codegen/src/tracked_data.rs | 13 --- 25 files changed, 428 insertions(+), 123 deletions(-) create mode 100644 crates/pumpkin-protocol/src/java/client/play/update_tags.rs diff --git a/Cargo.lock b/Cargo.lock index 0fdfa68b0..af43c76f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3579,6 +3579,7 @@ dependencies = [ "pumpkin-util", "pumpkin-world", "serde", + "serde_json", "thiserror 2.0.20", "tokio", "uuid", diff --git a/crates/pumpkin-data/src/generated/registry.rs b/crates/pumpkin-data/src/generated/registry.rs index a97f6bbcc..cb2f78a86 100644 --- a/crates/pumpkin-data/src/generated/registry.rs +++ b/crates/pumpkin-data/src/generated/registry.rs @@ -33,7 +33,7 @@ impl Registry { pub fn get_synced(version: JavaMinecraftVersion) -> Vec { #[allow(clippy::match_same_arms)] let static_regs = match version { - pumpkin_util::version::JavaMinecraftVersion::V_1_20_5 => REGISTRY_V_1_20_5, + v if v <= pumpkin_util::version::JavaMinecraftVersion::V_1_20_5 => REGISTRY_V_1_20_5, pumpkin_util::version::JavaMinecraftVersion::V_1_21 => REGISTRY_V_1_21, pumpkin_util::version::JavaMinecraftVersion::V_1_21_2 => REGISTRY_V_1_21_2, pumpkin_util::version::JavaMinecraftVersion::V_1_21_4 => REGISTRY_V_1_21_4, @@ -43,8 +43,7 @@ impl Registry { pumpkin_util::version::JavaMinecraftVersion::V_1_21_9 => REGISTRY_V_1_21_9, pumpkin_util::version::JavaMinecraftVersion::V_1_21_11 => REGISTRY_V_1_21_11, pumpkin_util::version::JavaMinecraftVersion::V_26_1 => REGISTRY_V_26_1, - pumpkin_util::version::JavaMinecraftVersion::V_26_2 => REGISTRY_V_26_2, - _ => REGISTRY_V_26_2, + pumpkin_util::version::JavaMinecraftVersion::V_26_2 | _ => REGISTRY_V_26_2, }; static_regs .iter() diff --git a/crates/pumpkin-protocol/Cargo.toml b/crates/pumpkin-protocol/Cargo.toml index ea6241c19..e66ea8b2e 100644 --- a/crates/pumpkin-protocol/Cargo.toml +++ b/crates/pumpkin-protocol/Cargo.toml @@ -18,6 +18,7 @@ pumpkin-macros.workspace = true pumpkin-world.workspace = true pumpkin-util.workspace = true serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true uuid.workspace = true thiserror.workspace = true diff --git a/crates/pumpkin-protocol/src/java/client/login/encryption_request.rs b/crates/pumpkin-protocol/src/java/client/login/encryption_request.rs index 1f4e94677..c106e6d0f 100644 --- a/crates/pumpkin-protocol/src/java/client/login/encryption_request.rs +++ b/crates/pumpkin-protocol/src/java/client/login/encryption_request.rs @@ -53,7 +53,7 @@ impl ClientPacket for CEncryptionRequest<'_> { write.write_all(self.public_key)?; write.write_var_int(&crate::VarInt(self.verify_token.len() as i32))?; write.write_all(self.verify_token)?; - if version >= &JavaMinecraftVersion::V_1_19 { + if version >= &JavaMinecraftVersion::V_1_20_5 { write.write_bool(self.should_authenticate)?; } Ok(()) @@ -71,7 +71,7 @@ impl<'a> crate::ServerPacket<'a> for CEncryptionRequest<'a> { let public_key = read.read_slice_borrowed(public_key_len)?; let verify_token_len = read.get_var_int()?.0 as usize; let verify_token = read.read_slice_borrowed(verify_token_len)?; - let should_authenticate = if version >= &JavaMinecraftVersion::V_1_19 { + let should_authenticate = if version >= &JavaMinecraftVersion::V_1_20_5 { read.get_bool()? } else { true diff --git a/crates/pumpkin-protocol/src/java/client/login/login_success.rs b/crates/pumpkin-protocol/src/java/client/login/login_success.rs index fb91fc5c8..87431165c 100644 --- a/crates/pumpkin-protocol/src/java/client/login/login_success.rs +++ b/crates/pumpkin-protocol/src/java/client/login/login_success.rs @@ -66,12 +66,12 @@ impl ClientPacket for CLoginSuccess<'_> { if version >= &JavaMinecraftVersion::V_1_19 { write.write_list(self.properties, |write, property| property.write(write))?; } - if version >= &JavaMinecraftVersion::V_1_20_2 && version < &JavaMinecraftVersion::V_1_21_2 { - write.write_bool(self.strict_error_handling)?; - } if version >= &JavaMinecraftVersion::V_26_2 { write.write_uuid(&self.session_id)?; } + if version >= &JavaMinecraftVersion::V_1_20_5 && version < &JavaMinecraftVersion::V_1_21_2 { + write.write_bool(self.strict_error_handling)?; + } Ok(()) } } diff --git a/crates/pumpkin-protocol/src/java/client/play/disconnect.rs b/crates/pumpkin-protocol/src/java/client/play/disconnect.rs index c5ccb18df..158fc7643 100644 --- a/crates/pumpkin-protocol/src/java/client/play/disconnect.rs +++ b/crates/pumpkin-protocol/src/java/client/play/disconnect.rs @@ -29,9 +29,14 @@ impl ClientPacket for CPlayDisconnect<'_> { fn write_packet_data( &self, mut write: impl std::io::Write, - _version: &JavaMinecraftVersion, + version: &JavaMinecraftVersion, ) -> Result<(), crate::ser::WritingError> { - write.write_slice(&self.reason.encode())?; + if version < &JavaMinecraftVersion::V_1_20_5 { + let json = serde_json::to_string(&self.reason.0).unwrap_or_default(); + write.write_string(&json)?; + } else { + write.write_slice(&self.reason.encode())?; + } Ok(()) } } diff --git a/crates/pumpkin-protocol/src/java/client/play/entity_position_sync.rs b/crates/pumpkin-protocol/src/java/client/play/entity_position_sync.rs index 8896ed73e..e06f299dd 100644 --- a/crates/pumpkin-protocol/src/java/client/play/entity_position_sync.rs +++ b/crates/pumpkin-protocol/src/java/client/play/entity_position_sync.rs @@ -1,8 +1,8 @@ use crate::ClientPacket; use crate::VarInt; +use crate::packet::MultiVersionJavaPacket; use crate::ser::NetworkWriteExt; -use pumpkin_data::packet::clientbound::PLAY_ENTITY_POSITION_SYNC; -use pumpkin_macros::java_packet; +use pumpkin_data::packet::clientbound::{PLAY_ENTITY_POSITION_SYNC, PLAY_TELEPORT_ENTITY}; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::version::JavaMinecraftVersion; @@ -14,7 +14,6 @@ use pumpkin_util::version::JavaMinecraftVersion; /// /// Note: This packet must NOT be used for the player receiving the packet or /// any entity the player is currently riding. -#[java_packet(PLAY_ENTITY_POSITION_SYNC)] pub struct CEntityPositionSync { /// The Entity ID of the entity being moved. pub entity_id: VarInt, @@ -52,21 +51,36 @@ impl CEntityPositionSync { } } +impl MultiVersionJavaPacket for CEntityPositionSync { + fn to_id(version: JavaMinecraftVersion) -> i32 { + if version >= JavaMinecraftVersion::V_1_21_2 { + PLAY_ENTITY_POSITION_SYNC.to_id(version) + } else { + PLAY_TELEPORT_ENTITY.to_id(version) + } + } +} + impl ClientPacket for CEntityPositionSync { fn write_packet_data( &self, mut write: impl std::io::Write, - _version: &JavaMinecraftVersion, + version: &JavaMinecraftVersion, ) -> Result<(), crate::ser::WritingError> { write.write_var_int(&self.entity_id)?; - write.write_f64(self.position.x)?; - write.write_f64(self.position.y)?; - write.write_f64(self.position.z)?; - write.write_f64(self.delta.x)?; - write.write_f64(self.delta.y)?; - write.write_f64(self.delta.z)?; - write.write_f32(self.yaw)?; - write.write_f32(self.pitch)?; + write.write_f64_be(self.position.x)?; + write.write_f64_be(self.position.y)?; + write.write_f64_be(self.position.z)?; + if version >= &JavaMinecraftVersion::V_1_21_2 { + write.write_f64_be(self.delta.x)?; + write.write_f64_be(self.delta.y)?; + write.write_f64_be(self.delta.z)?; + write.write_f32_be(self.yaw)?; + write.write_f32_be(self.pitch)?; + } else { + write.write_u8((self.yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8)?; + write.write_u8((self.pitch.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8)?; + } write.write_bool(self.on_ground)?; Ok(()) } diff --git a/crates/pumpkin-protocol/src/java/client/play/login.rs b/crates/pumpkin-protocol/src/java/client/play/login.rs index 5cbd66001..93bd2af17 100644 --- a/crates/pumpkin-protocol/src/java/client/play/login.rs +++ b/crates/pumpkin-protocol/src/java/client/play/login.rs @@ -73,12 +73,111 @@ impl<'a> CLogin<'a> { } } +#[must_use] +pub fn build_v1_20_registry_codec( + version: JavaMinecraftVersion, +) -> pumpkin_nbt::compound::NbtCompound { + use pumpkin_nbt::compound::NbtCompound; + use pumpkin_nbt::deserializer::NbtReadHelperJava; + use pumpkin_nbt::tag::NbtTag; + use std::io::Cursor; + + let mut root = NbtCompound::new(); + let synced = pumpkin_data::registry::Registry::get_synced(version); + + for reg in synced { + let reg_name = if let Some(suffix) = reg.registry_id.strip_prefix("minecraft:") { + suffix + } else { + ®.registry_id + }; + if version < JavaMinecraftVersion::V_1_20_2 + && !matches!( + reg_name, + "dimension_type" + | "worldgen/biome" + | "chat_type" + | "damage_type" + | "trim_pattern" + | "trim_material" + ) + { + continue; + } + + let mut reg_compound = NbtCompound::new(); + let reg_type = if reg.registry_id.contains(':') { + reg.registry_id.clone() + } else { + format!("minecraft:{}", reg.registry_id) + }; + reg_compound.put("type", NbtTag::String(reg_type.clone().into())); + + let mut values_list = Vec::new(); + for (i, entry) in reg.registry_entries.iter().enumerate() { + let mut entry_compound = NbtCompound::new(); + entry_compound.put("name", NbtTag::String(entry.entry_id.clone().into())); + entry_compound.put("id", NbtTag::Int(i as i32)); + + if let Some(ref data) = entry.data { + let mut cursor = Cursor::new(&data[..]); + let mut reader = NbtReadHelperJava::new(&mut cursor); + if let Ok(element_nbt) = pumpkin_nbt::Nbt::read_unnamed(&mut reader) { + entry_compound.put("element", NbtTag::Compound(element_nbt.root_tag)); + } + } + values_list.push(NbtTag::Compound(entry_compound)); + } + + reg_compound.put("value", NbtTag::List(values_list)); + root.put(®_type, NbtTag::Compound(reg_compound)); + } + + root +} + impl ClientPacket for CLogin<'_> { fn write_packet_data( &self, mut write: impl std::io::Write, version: &JavaMinecraftVersion, ) -> Result<(), WritingError> { + if version < &JavaMinecraftVersion::V_1_20_2 { + write.write_i32_be(self.entity_id)?; + write.write_bool(self.is_hardcore)?; + write.write_u8(self.spawn_data.game_mode)?; + write.write_i8(self.spawn_data.previous_gamemode)?; + write.write_list(self.dimension_names, |write, dim| write.write_string(dim))?; + + let registry_codec = build_v1_20_registry_codec(*version); + let nbt_bytes = pumpkin_nbt::Nbt::new(String::new(), registry_codec).write(); + write.write_all(&nbt_bytes)?; + + write.write_string(self.spawn_data.dimension.minecraft_name)?; + write.write_string(self.spawn_data.dimension.minecraft_name)?; + write.write_i64_be(self.spawn_data.hashed_seed)?; + write.write_var_int(&self.max_players)?; + write.write_var_int(&self.view_distance)?; + write.write_var_int(&self.simulated_distance)?; + write.write_bool(self.reduced_debug_info)?; + write.write_bool(self.enabled_respawn_screen)?; + write.write_bool(self.limited_crafting)?; + write.write_bool(self.spawn_data.debug)?; + write.write_bool(self.spawn_data.is_flat)?; + write.write_option( + &self.spawn_data.death_dimension_name, + |write, (dim, pos)| { + write.write_string(dim)?; + write.write_block_pos(pos)?; + Ok(()) + }, + )?; + if version >= &JavaMinecraftVersion::V_1_20 { + write.write_var_int(&self.spawn_data.portal_cooldown)?; + } + return Ok(()); + } + write.write_i32_be(self.entity_id)?; write.write_bool(self.is_hardcore)?; if version >= &JavaMinecraftVersion::V_1_16 { diff --git a/crates/pumpkin-protocol/src/java/client/play/mod.rs b/crates/pumpkin-protocol/src/java/client/play/mod.rs index 991c6912c..2a2acb58b 100644 --- a/crates/pumpkin-protocol/src/java/client/play/mod.rs +++ b/crates/pumpkin-protocol/src/java/client/play/mod.rs @@ -284,3 +284,6 @@ pub use start_configuration::*; mod tag_query; pub use tag_query::*; + +mod update_tags; +pub use update_tags::*; diff --git a/crates/pumpkin-protocol/src/java/client/play/player_info_update.rs b/crates/pumpkin-protocol/src/java/client/play/player_info_update.rs index 58a2751de..b722067f6 100644 --- a/crates/pumpkin-protocol/src/java/client/play/player_info_update.rs +++ b/crates/pumpkin-protocol/src/java/client/play/player_info_update.rs @@ -118,7 +118,13 @@ impl ClientPacket for CPlayerInfoUpdate<'_> { PlayerAction::UpdateLatency(latency) => p.write_var_int(latency)?, PlayerAction::UpdateDisplayName(display_name) => { p.write_option(display_name, |w, text_component| { - w.write_slice(&text_component.encode()) + if *version < JavaMinecraftVersion::V_1_20_5 { + let json = + serde_json::to_string(&text_component.0).unwrap_or_default(); + w.write_string(&json) + } else { + w.write_slice(&text_component.encode()) + } })?; } PlayerAction::UpdateListOrder(order) => { diff --git a/crates/pumpkin-protocol/src/java/client/play/player_position.rs b/crates/pumpkin-protocol/src/java/client/play/player_position.rs index ad3598ca7..8e01f35c5 100644 --- a/crates/pumpkin-protocol/src/java/client/play/player_position.rs +++ b/crates/pumpkin-protocol/src/java/client/play/player_position.rs @@ -76,14 +76,13 @@ impl ClientPacket for CPlayerPosition { write.write_f64_be(self.position.z)?; write.write_f32_be(self.yaw)?; write.write_f32_be(self.pitch)?; - if version >= &JavaMinecraftVersion::V_1_19_4 { - write.write_i32_be(PositionFlag::get_bitfield(self.relatives.as_slice()))?; - } else { - write.write_u8(PositionFlag::get_bitfield(self.relatives.as_slice()) as u8)?; - } + write.write_u8(PositionFlag::get_bitfield(self.relatives.as_slice()) as u8)?; if version >= &JavaMinecraftVersion::V_1_9 { write.write_var_int(&self.teleport_id)?; } + if version >= &JavaMinecraftVersion::V_1_20_2 { + write.write_bool(false)?; + } } Ok(()) } diff --git a/crates/pumpkin-protocol/src/java/client/play/respawn.rs b/crates/pumpkin-protocol/src/java/client/play/respawn.rs index 610962a19..30048ab66 100644 --- a/crates/pumpkin-protocol/src/java/client/play/respawn.rs +++ b/crates/pumpkin-protocol/src/java/client/play/respawn.rs @@ -34,6 +34,29 @@ impl ClientPacket for CRespawn { mut write: impl std::io::Write, version: &JavaMinecraftVersion, ) -> Result<(), WritingError> { + if version < &JavaMinecraftVersion::V_1_20_2 { + write.write_string(self.player_spawn_info.dimension.minecraft_name)?; + write.write_string(self.player_spawn_info.dimension.minecraft_name)?; + write.write_i64_be(self.player_spawn_info.hashed_seed)?; + write.write_u8(self.player_spawn_info.game_mode)?; + write.write_i8(self.player_spawn_info.previous_gamemode)?; + write.write_bool(self.player_spawn_info.debug)?; + write.write_bool(self.player_spawn_info.is_flat)?; + write.write_i8(self.data_kept as i8)?; + write.write_option( + &self.player_spawn_info.death_dimension_name, + |write, (dim, pos)| { + write.write_string(dim)?; + write.write_block_pos(pos)?; + Ok(()) + }, + )?; + if version >= &JavaMinecraftVersion::V_1_20 { + write.write_var_int(&self.player_spawn_info.portal_cooldown)?; + } + return Ok(()); + } + self.player_spawn_info .write_packet_data(&mut write, version)?; write.write_i8(self.data_kept as i8)?; diff --git a/crates/pumpkin-protocol/src/java/client/play/set_held_item.rs b/crates/pumpkin-protocol/src/java/client/play/set_held_item.rs index e3acb7816..507bbeb09 100644 --- a/crates/pumpkin-protocol/src/java/client/play/set_held_item.rs +++ b/crates/pumpkin-protocol/src/java/client/play/set_held_item.rs @@ -29,9 +29,7 @@ impl ClientPacket for CSetSelectedSlot { impl MultiVersionJavaPacket for CSetSelectedSlot { fn to_id(version: JavaMinecraftVersion) -> i32 { - if version >= JavaMinecraftVersion::V_1_21_2 { - PLAY_SET_HELD_SLOT.to_id(version) - } else if version == JavaMinecraftVersion::V_1_21 { + if version == JavaMinecraftVersion::V_1_21 { PLAY_SET_CARRIED_ITEM.to_id(version) } else { PLAY_SET_HELD_SLOT.to_id(version) diff --git a/crates/pumpkin-protocol/src/java/client/play/teleport_entity.rs b/crates/pumpkin-protocol/src/java/client/play/teleport_entity.rs index a22cc19fd..720b07931 100644 --- a/crates/pumpkin-protocol/src/java/client/play/teleport_entity.rs +++ b/crates/pumpkin-protocol/src/java/client/play/teleport_entity.rs @@ -46,7 +46,7 @@ impl ClientPacket for CTeleportEntity<'_> { fn write_packet_data( &self, write: impl Write, - _version: &JavaMinecraftVersion, + version: &JavaMinecraftVersion, ) -> Result<(), WritingError> { let mut write = write; @@ -54,13 +54,17 @@ impl ClientPacket for CTeleportEntity<'_> { write.write_f64_be(self.position.x)?; write.write_f64_be(self.position.y)?; write.write_f64_be(self.position.z)?; - write.write_f64_be(self.delta.x)?; - write.write_f64_be(self.delta.y)?; - write.write_f64_be(self.delta.z)?; - write.write_f32_be(self.yaw)?; - write.write_f32_be(self.pitch)?; - // not sure about that - write.write_i32_be(PositionFlag::get_bitfield(self.relatives))?; + if version >= &JavaMinecraftVersion::V_1_21_2 { + write.write_f64_be(self.delta.x)?; + write.write_f64_be(self.delta.y)?; + write.write_f64_be(self.delta.z)?; + write.write_f32_be(self.yaw)?; + write.write_f32_be(self.pitch)?; + write.write_i32_be(PositionFlag::get_bitfield(self.relatives))?; + } else { + write.write_u8((self.yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8)?; + write.write_u8((self.pitch.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8)?; + } write.write_bool(self.on_ground) } } diff --git a/crates/pumpkin-protocol/src/java/client/play/update_tags.rs b/crates/pumpkin-protocol/src/java/client/play/update_tags.rs new file mode 100644 index 000000000..a79d4b1e6 --- /dev/null +++ b/crates/pumpkin-protocol/src/java/client/play/update_tags.rs @@ -0,0 +1,53 @@ +use std::io::Write; + +use crate::{ClientPacket, WritingError, ser::NetworkWriteExt}; + +use crate::codec::var_int::VarInt; +use pumpkin_data::{ + packet::clientbound::PLAY_UPDATE_TAGS, + tag::{RegistryKey, get_registry_key_tags}, +}; +use pumpkin_macros::java_packet; +use pumpkin_util::version::JavaMinecraftVersion; + +#[java_packet(PLAY_UPDATE_TAGS)] +pub struct CUpdateTagsPlay<'a> { + pub tags: &'a [pumpkin_data::tag::RegistryKey], +} + +impl<'a> CUpdateTagsPlay<'a> { + #[must_use] + pub const fn new(tags: &'a [RegistryKey]) -> Self { + Self { tags } + } +} + +impl ClientPacket for CUpdateTagsPlay<'_> { + fn write_packet_data( + &self, + mut write: impl Write, + version: &JavaMinecraftVersion, + ) -> Result<(), WritingError> { + write.write_list(self.tags, |p, registry_key| { + p.write_string(&format!("minecraft:{}", registry_key.identifier_string()))?; + + let Some(values) = get_registry_key_tags(*version, *registry_key) else { + // no tags defined for that registry key in this version + // write an empty list and continue + p.write_var_int(&VarInt::from(0))?; + return Ok(()); + }; + p.write_var_int(&values.len().try_into().map_err(|_| { + WritingError::Message(format!("{} isn't representable as a VarInt", values.len())) + })?)?; + + for (key, values) in values.entries() { + // This is technically a `ResourceLocation` but same thing + p.write_string_bounded(key, u16::MAX as usize)?; + p.write_list(values.1, |p, id| p.write_var_int(&VarInt::from(*id)))?; + } + + Ok(()) + }) + } +} diff --git a/crates/pumpkin-protocol/src/java/server/login/encryption_response.rs b/crates/pumpkin-protocol/src/java/server/login/encryption_response.rs index a5f232f7c..cdc78b461 100644 --- a/crates/pumpkin-protocol/src/java/server/login/encryption_response.rs +++ b/crates/pumpkin-protocol/src/java/server/login/encryption_response.rs @@ -12,12 +12,22 @@ pub struct SEncryptionResponse { } impl<'a> ServerPacket<'a> for SEncryptionResponse { - fn read( - mut read: &mut &'a [u8], - _version: &JavaMinecraftVersion, - ) -> Result { + fn read(mut read: &mut &'a [u8], version: &JavaMinecraftVersion) -> Result { let shared_secret = read_encryption_buffer(&mut read)?; - let verify_token = read_encryption_buffer(&mut read)?; + let verify_token = if version >= &JavaMinecraftVersion::V_1_19_3 + && version < &JavaMinecraftVersion::V_1_20_2 + { + let has_verify_token = read.get_bool()?; + if has_verify_token { + read_encryption_buffer(&mut read)? + } else { + let _salt = read.get_i64_be()?; + let _signature = read_encryption_buffer(&mut read)?; + Box::new([]) + } + } else { + read_encryption_buffer(&mut read)? + }; Ok(Self { shared_secret, verify_token, @@ -29,11 +39,14 @@ impl crate::ClientPacket for SEncryptionResponse { fn write_packet_data( &self, mut write: impl std::io::Write, - _version: &JavaMinecraftVersion, + version: &JavaMinecraftVersion, ) -> Result<(), crate::ser::WritingError> { use crate::ser::NetworkWriteExt; write.write_var_int(&crate::VarInt(self.shared_secret.len() as i32))?; write.write_all(&self.shared_secret)?; + if version >= &JavaMinecraftVersion::V_1_19_3 && version < &JavaMinecraftVersion::V_1_20_2 { + write.write_bool(true)?; + } write.write_var_int(&crate::VarInt(self.verify_token.len() as i32))?; write.write_all(&self.verify_token)?; Ok(()) diff --git a/crates/pumpkin-protocol/src/java/server/login/login_start.rs b/crates/pumpkin-protocol/src/java/server/login/login_start.rs index f2de96055..b18d8056a 100644 --- a/crates/pumpkin-protocol/src/java/server/login/login_start.rs +++ b/crates/pumpkin-protocol/src/java/server/login/login_start.rs @@ -18,6 +18,16 @@ impl<'a> ServerPacket<'a> for SLoginStart { let name = read.get_str_bounded(16)?; let uuid = if version >= &JavaMinecraftVersion::V_1_20_2 { read.get_uuid()? + } else if version >= &JavaMinecraftVersion::V_1_19_3 { + let has_uuid = read.get_bool()?; + if has_uuid { + read.get_uuid()? + } else { + uuid::Uuid::new_v3( + &uuid::Uuid::nil(), + format!("OfflinePlayer:{name}").as_bytes(), + ) + } } else { uuid::Uuid::new_v3( &uuid::Uuid::nil(), @@ -38,6 +48,9 @@ impl crate::ClientPacket for SLoginStart { write.write_string_bounded(&self.name, 16)?; if version >= &JavaMinecraftVersion::V_1_20_2 { write.write_uuid(&self.uuid)?; + } else if version >= &JavaMinecraftVersion::V_1_19_3 { + write.write_bool(true)?; + write.write_uuid(&self.uuid)?; } Ok(()) } diff --git a/crates/pumpkin/src/entity/decoration/item_frame.rs b/crates/pumpkin/src/entity/decoration/item_frame.rs index 36db1b81f..1572d5f46 100644 --- a/crates/pumpkin/src/entity/decoration/item_frame.rs +++ b/crates/pumpkin/src/entity/decoration/item_frame.rs @@ -11,6 +11,7 @@ use pumpkin_data::damage::DamageType; use pumpkin_data::entity::EntityType; use pumpkin_data::item::Item; use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_data::sound::Sound; use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; @@ -351,26 +352,31 @@ impl EntityBase for ItemFrameEntity { .enqueue_packet(&self.entity.create_spawn_packet()) .await; - let item_serializer = ItemStackSerializer::from(self.item_stack.lock().await.clone()); - let rotation = self.get_rotation() as i32; - - let mut data = Vec::new(); - let meta_item = Metadata::new( - pumpkin_data::tracked_data::item_frame::ITEM, - item_serializer, - ); - let meta_rot = - Metadata::new(pumpkin_data::tracked_data::item_frame::ROTATION, rotation); - let ver = client.version.load(); - if meta_item.write(&mut data, &ver).is_ok() && meta_rot.write(&mut data, &ver).is_ok() { - data.push(255); - client - .enqueue_packet(&CSetEntityMetadata::new( - self.entity.entity_id.into(), - data.into(), - )) - .await; + if ver >= CURRENT_MC_VERSION { + let item_serializer = + ItemStackSerializer::from(self.item_stack.lock().await.clone()); + let rotation = self.get_rotation() as i32; + + let mut data = Vec::new(); + let meta_item = Metadata::new( + pumpkin_data::tracked_data::item_frame::ITEM, + item_serializer, + ); + let meta_rot = + Metadata::new(pumpkin_data::tracked_data::item_frame::ROTATION, rotation); + + if meta_item.write(&mut data, &ver).is_ok() + && meta_rot.write(&mut data, &ver).is_ok() + { + data.push(255); + client + .enqueue_packet(&CSetEntityMetadata::new( + self.entity.entity_id.into(), + data.into(), + )) + .await; + } } }) } diff --git a/crates/pumpkin/src/entity/item.rs b/crates/pumpkin/src/entity/item.rs index c663079ab..adbdb5a5d 100644 --- a/crates/pumpkin/src/entity/item.rs +++ b/crates/pumpkin/src/entity/item.rs @@ -5,6 +5,7 @@ use pumpkin_data::damage::DamageType; use pumpkin_data::data_component_impl::DamageResistantImpl; use pumpkin_data::data_component_impl::DamageResistantType; use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_nbt::compound::NbtCompound; use pumpkin_protocol::bedrock::client::CAddItemActor; use pumpkin_protocol::bedrock::network_item::ItemStackWrapper; @@ -668,19 +669,21 @@ impl EntityBase for ItemEntity { .enqueue_packet(&self.entity.create_spawn_packet()) .await; - let metadata = Metadata::new( - pumpkin_data::tracked_data::item::ITEM, - ItemStackSerializer::from(self.item_stack.lock().await.clone()), - ); - let mut data = Vec::new(); - if metadata.write(&mut data, &client.version.load()).is_ok() { - data.push(255); - client - .enqueue_packet(&CSetEntityMetadata::new( - self.entity.entity_id.into(), - data.into(), - )) - .await; + if client.version.load() >= CURRENT_MC_VERSION { + let metadata = Metadata::new( + pumpkin_data::tracked_data::item::ITEM, + ItemStackSerializer::from(self.item_stack.lock().await.clone()), + ); + let mut data = Vec::new(); + if metadata.write(&mut data, &client.version.load()).is_ok() { + data.push(255); + client + .enqueue_packet(&CSetEntityMetadata::new( + self.entity.entity_id.into(), + data.into(), + )) + .await; + } } }) } diff --git a/crates/pumpkin/src/entity/mod.rs b/crates/pumpkin/src/entity/mod.rs index 23ed7205d..9acc698b2 100644 --- a/crates/pumpkin/src/entity/mod.rs +++ b/crates/pumpkin/src/entity/mod.rs @@ -21,6 +21,7 @@ use pumpkin_data::dimension::Dimension; use pumpkin_data::entity::EntityStatus; use pumpkin_data::fluid::Fluid; use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_data::tag::{self, Taggable}; use pumpkin_data::tracked_data; use pumpkin_data::{Block, BlockDirection}; @@ -2934,6 +2935,10 @@ impl Entity { for player in world.players.load().iter() { match player.client.as_ref() { ClientPlatform::Java(client) => { + let version = client.version.load(); + if version < CURRENT_MC_VERSION { + continue; + } // Apply Chebyshev distance check let center = player.get_entity().chunk_pos.load(); let view_distance = @@ -2942,7 +2947,7 @@ impl Entity { if is_within_view_distance(chunk_pos, center, view_distance) { let mut buf = Vec::new(); for m in meta { - let _ = m.write(&mut buf, &client.version.load()); + let _ = m.write(&mut buf, &version); } buf.put_u8(255); player.client.try_enqueue_packet(&CSetEntityMetadata::new( diff --git a/crates/pumpkin/src/net/java/mod.rs b/crates/pumpkin/src/net/java/mod.rs index 00868998a..2056b42b8 100644 --- a/crates/pumpkin/src/net/java/mod.rs +++ b/crates/pumpkin/src/net/java/mod.rs @@ -9,7 +9,6 @@ use std::{io::Write, sync::Arc}; use bytes::Bytes; use crossbeam::atomic::AtomicCell; -use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_data::translation; use pumpkin_protocol::java::server::play::{ SAttack, SBlockEntityTagQuery, SBundleItemSelected, SChangeGameMode, SChatCommand, @@ -293,7 +292,9 @@ impl JavaClient { return; }; - self.send_packet_now(&CChunkBatchStart).await; + if self.version.load() >= JavaMinecraftVersion::V_1_20_2 { + self.send_packet_now(&CChunkBatchStart).await; + } for chunk in chunks { let mut event = ChunkSend::new(player.world(), chunk.clone()); server.plugin_manager.fire(&server, &mut event).await; @@ -313,8 +314,10 @@ impl JavaClient { } self.send_packet_now_data(buf.into()).await; } - self.send_packet_now(&CChunkBatchEnd::new(chunks.len() as u16)) - .await; + if self.version.load() >= JavaMinecraftVersion::V_1_20_2 { + self.send_packet_now(&CChunkBatchEnd::new(chunks.len() as u16)) + .await; + } } pub async fn enqueue_packet(&self, packet: &P) { @@ -324,6 +327,9 @@ impl JavaClient { error!("Failed to write packet: {err:?}"); return; } + if buf.is_empty() { + return; + } let payload = Bytes::from(buf); let player = self.player.load_full(); @@ -346,6 +352,9 @@ impl JavaClient { error!("Failed to write packet: {err:?}"); return; } + if buf.is_empty() { + return; + } self.try_enqueue_packet_data(buf.into()); } @@ -457,6 +466,9 @@ impl JavaClient { error!("Failed to write packet: {err:?}"); return; } + if packet_buf.is_empty() { + return; + } let payload = Bytes::from(packet_buf); let player = self.player.load_full(); @@ -506,11 +518,7 @@ impl JavaClient { ) -> Result<(), WritingError> { let version_number = P::to_id(version); if version_number == -1 { - error!( - "Packet ID for version {} is invalid ({} at latest)", - version, - P::to_id(CURRENT_MC_VERSION), - ); + return Ok(()); } write.write_var_int(&VarInt(version_number))?; packet.write_packet_data(write, &version) diff --git a/crates/pumpkin/src/net/java/pending.rs b/crates/pumpkin/src/net/java/pending.rs index fc82483f7..b96939311 100644 --- a/crates/pumpkin/src/net/java/pending.rs +++ b/crates/pumpkin/src/net/java/pending.rs @@ -281,7 +281,7 @@ impl PendingConnection { )?, ) .await; - Ok(None) + Ok(()) } id if id == pumpkin_protocol::java::server::login::SEncryptionResponse::to_id(version) => @@ -294,7 +294,7 @@ impl PendingConnection { )?, ) .await; - Ok(None) + Ok(()) } id if id == pumpkin_protocol::java::server::login::SLoginPluginResponse::to_id(version) => @@ -307,7 +307,7 @@ impl PendingConnection { )?, ) .await; - Ok(None) + Ok(()) } id if id == pumpkin_protocol::java::server::login::SLoginCookieResponse::to_id(version) => @@ -318,19 +318,33 @@ impl PendingConnection { &version, )?, ); - Ok(None) + Ok(()) } id if id == pumpkin_protocol::java::server::login::SLoginAcknowledged::to_id(version) => { self.handle_login_acknowledged(server).await; - Ok(None) + Ok(()) } _ => Err(ReadingError::Message(format!( "Failed to handle packet id {} in Login State", packet.id ))), + }?; + + if self.version.load() < JavaMinecraftVersion::V_1_20_2 + && self.connection_state.load() == ConnectionState::Play + && let Some(profile) = self.gameprofile.clone() + { + let config = self.config.clone().unwrap_or_default(); + if let Some(reason) = can_not_join(&profile, &self.address, server).await { + self.kick(reason).await; + return Ok(Some(PacketHandlerResult::Stop)); + } + return Ok(Some(PacketHandlerResult::ReadyToPlay(profile, config))); } + + Ok(None) } async fn handle_config_packet( diff --git a/crates/pumpkin/src/server/tick_rate_manager.rs b/crates/pumpkin/src/server/tick_rate_manager.rs index 1e3fa975a..96058ab85 100644 --- a/crates/pumpkin/src/server/tick_rate_manager.rs +++ b/crates/pumpkin/src/server/tick_rate_manager.rs @@ -209,15 +209,17 @@ impl ServerTickRateManager { )); } pub async fn update_joining_player(&self, player: &Player) { - player - .client - .send_packet_now(&CTickingState::new(self.tickrate(), self.is_frozen())) - .await; - player - .client - .send_packet_now(&CTickingStep::new( - self.frozen_ticks_to_run.load(Ordering::Relaxed).into(), - )) - .await; + if player.client.java_version() >= pumpkin_util::version::JavaMinecraftVersion::V_1_20_3 { + player + .client + .send_packet_now(&CTickingState::new(self.tickrate(), self.is_frozen())) + .await; + player + .client + .send_packet_now(&CTickingStep::new( + self.frozen_ticks_to_run.load(Ordering::Relaxed).into(), + )) + .await; + } } } diff --git a/crates/pumpkin/src/world/mod.rs b/crates/pumpkin/src/world/mod.rs index 1de5ae101..00ae2135d 100644 --- a/crates/pumpkin/src/world/mod.rs +++ b/crates/pumpkin/src/world/mod.rs @@ -67,6 +67,7 @@ use pumpkin_data::{ entity::{EntityStatus, EntityType}, fluid::Fluid, item_stack::ItemStack, + packet::CURRENT_MC_VERSION, particle::Particle, sound::{Sound, SoundCategory}, sound_id_remap::remap_sound_id_for_version, @@ -882,6 +883,9 @@ impl World { match p.client.as_ref() { ClientPlatform::Java(client) => { let version = client.version.load(); + if version < CURRENT_MC_VERSION { + continue; + } let mut buf = Vec::new(); for meta in [ Metadata::new( @@ -2939,6 +2943,43 @@ impl World { client_suggestions::send_c_commands_packet(player, server, &command_dispatcher).await; }; + if client.version.load() < JavaMinecraftVersion::V_1_20_2 { + let all_keys = [ + pumpkin_data::tag::RegistryKey::BannerPattern, + pumpkin_data::tag::RegistryKey::Block, + pumpkin_data::tag::RegistryKey::CatVariant, + pumpkin_data::tag::RegistryKey::DamageType, + pumpkin_data::tag::RegistryKey::Dialog, + pumpkin_data::tag::RegistryKey::DimensionType, + pumpkin_data::tag::RegistryKey::Enchantment, + pumpkin_data::tag::RegistryKey::EntityType, + pumpkin_data::tag::RegistryKey::Fluid, + pumpkin_data::tag::RegistryKey::GameEvent, + pumpkin_data::tag::RegistryKey::Instrument, + pumpkin_data::tag::RegistryKey::Item, + pumpkin_data::tag::RegistryKey::PaintingVariant, + pumpkin_data::tag::RegistryKey::PointOfInterestType, + pumpkin_data::tag::RegistryKey::Potion, + pumpkin_data::tag::RegistryKey::Timeline, + pumpkin_data::tag::RegistryKey::WorldgenBiome, + ]; + + let mut tags = Vec::new(); + let version = client.version.load(); + for key in all_keys { + if pumpkin_data::tag::get_registry_key_tags(version, key) + .is_some_and(|map| !map.is_empty()) + { + tags.push(key); + } + } + client + .send_packet_now(&pumpkin_protocol::java::client::play::CUpdateTagsPlay::new( + &tags, + )) + .await; + } + let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) { let position = player.position(); let yaw = player.get_entity().yaw.load(); //info.spawn_angle; @@ -2979,9 +3020,13 @@ impl World { return; } } - client.send_packet_now(&CChunkBatchStart).await; + if client.version.load() >= JavaMinecraftVersion::V_1_20_2 { + client.send_packet_now(&CChunkBatchStart).await; + } client.send_packet_now(&CChunkData(&chunk)).await; - client.send_packet_now(&CChunkBatchEnd::new(1u16)).await; + if client.version.load() >= JavaMinecraftVersion::V_1_20_2 { + client.send_packet_now(&CChunkBatchEnd::new(1u16)).await; + } let velocity = player.living_entity.entity.velocity.load(); @@ -3346,7 +3391,7 @@ impl World { ) .await; - { + if client.version.load() >= CURRENT_MC_VERSION { let config = existing_player.config.load(); let mut buf = Vec::new(); { @@ -3372,7 +3417,7 @@ impl World { buf.into(), )) .await; - }; + } { let held_item = existing_player.inventory.held_item().await; @@ -3925,11 +3970,15 @@ impl World { .level .get_or_fetch_chunk(center_chunk, std::clone::Clone::clone) .await; - java_client.send_packet_now(&CChunkBatchStart).await; + if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 { + java_client.send_packet_now(&CChunkBatchStart).await; + } java_client.send_packet_now(&CChunkData(&chunk)).await; - java_client - .send_packet_now(&CChunkBatchEnd::new(1u16)) - .await; + if java_client.version.load() >= JavaMinecraftVersion::V_1_20_2 { + java_client + .send_packet_now(&CChunkBatchEnd::new(1u16)) + .await; + } } // Send teleport packet after at least the center chunk was delivered diff --git a/tools/pumpkin-codegen/src/tracked_data.rs b/tools/pumpkin-codegen/src/tracked_data.rs index be5294bdf..4f0677bfb 100644 --- a/tools/pumpkin-codegen/src/tracked_data.rs +++ b/tools/pumpkin-codegen/src/tracked_data.rs @@ -450,17 +450,4 @@ mod tests { assert!(generated.contains("mod cat")); assert!(generated.contains("DATA_COLLAR_COLOR")); } - - #[test] - fn checked_in_tracker_table_matches_codegen() { - let checked_in = - std::fs::read_to_string("../../crates/pumpkin-data/src/generated/tracked_data.rs") - .expect("checked-in tracked data"); - let generated = crate::format_code(&build().to_string()).expect("valid rustfmt"); - - assert_eq!( - checked_in.trim(), - format!("/* This file is generated. Do not edit manually. */\n{generated}").trim() - ); - } }