From 43ebbee94a24e1f797759d3abe002b8ddfe0523c Mon Sep 17 00:00:00 2001 From: mat Date: Sat, 29 Mar 2025 23:10:55 +0000 Subject: [PATCH] update ServerboundContainerClick to use HashedStack from 1.21.5 --- Cargo.lock | 1 + Cargo.toml | 1 + azalea-client/src/plugins/inventory.rs | 15 ++-- azalea-inventory/src/slot.rs | 3 +- azalea-protocol/Cargo.toml | 4 +- .../azalea-protocol-macros/src/lib.rs | 5 +- .../src/packets/game/s_container_click.rs | 68 ++++++++++++++++++- 7 files changed, 85 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b80af7d..164d3e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,6 +492,7 @@ dependencies = [ "azalea-registry", "azalea-world", "bevy_ecs", + "crc32fast", "flate2", "futures", "futures-lite", diff --git a/Cargo.toml b/Cargo.toml index b5d085c6..0f11289d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ num-format = "0.4.4" indexmap = "2.7.1" paste = "1.0.15" compact_str = "0.8.1" +crc32fast = "1.4.2" # --- Profile Settings --- diff --git a/azalea-client/src/plugins/inventory.rs b/azalea-client/src/plugins/inventory.rs index da450ad7..d3002771 100644 --- a/azalea-client/src/plugins/inventory.rs +++ b/azalea-client/src/plugins/inventory.rs @@ -10,7 +10,8 @@ use azalea_inventory::{ }, }; use azalea_protocol::packets::game::{ - s_container_click::ServerboundContainerClick, s_container_close::ServerboundContainerClose, + s_container_click::{HashedStack, ServerboundContainerClick}, + s_container_close::ServerboundContainerClose, s_set_carried_item::ServerboundSetCarriedItem, }; use azalea_registry::MenuKind; @@ -23,7 +24,7 @@ use bevy_ecs::{ schedule::{IntoSystemConfigs, SystemSet}, system::{Commands, Query}, }; -use tracing::warn; +use tracing::{error, warn}; use super::packet::game::handle_outgoing_packets; use crate::{ @@ -667,8 +668,8 @@ pub fn handle_container_click_event( for event in events.read() { let (entity, mut inventory) = query.get_mut(event.entity).unwrap(); if inventory.id != event.window_id { - warn!( - "Tried to click container with ID {}, but the current container ID is {}", + error!( + "Tried to click container with ID {}, but the current container ID is {}. Click packet won't be sent.", event.window_id, inventory.id ); continue; @@ -681,11 +682,11 @@ pub fn handle_container_click_event( // see which slots changed after clicking and put them in the hashmap // the server uses this to check if we desynced - let mut changed_slots: HashMap = HashMap::new(); + let mut changed_slots: HashMap = HashMap::new(); for (slot_index, old_slot) in old_slots.iter().enumerate() { let new_slot = &menu.slots()[slot_index]; if old_slot != new_slot { - changed_slots.insert(slot_index as u16, new_slot.clone()); + changed_slots.insert(slot_index as u16, HashedStack::from(new_slot)); } } @@ -721,7 +722,7 @@ fn handle_set_container_content_event( if event.container_id != inventory.id { warn!( - "Tried to set container content with ID {}, but the current container ID is {}", + "Got SetContainerContentEvent for container with ID {}, but the current container ID is {}", event.container_id, inventory.id ); continue; diff --git a/azalea-inventory/src/slot.rs b/azalea-inventory/src/slot.rs index 2b886955..e2e84a68 100644 --- a/azalea-inventory/src/slot.rs +++ b/azalea-inventory/src/slot.rs @@ -178,7 +178,8 @@ impl AzaleaWrite for ItemStack { /// and Azalea does not implement that yet. #[derive(Default)] pub struct DataComponentPatch { - components: IndexMap>>, + pub components: + IndexMap>>, } impl DataComponentPatch { diff --git a/azalea-protocol/Cargo.toml b/azalea-protocol/Cargo.toml index e81beea6..78e776b6 100644 --- a/azalea-protocol/Cargo.toml +++ b/azalea-protocol/Cargo.toml @@ -48,8 +48,10 @@ tokio-util = { workspace = true, features = ["codec"] } tracing.workspace = true hickory-resolver = { workspace = true, features = ["tokio-runtime"] } uuid.workspace = true +crc32fast = { workspace = true, optional = true } [features] connecting = [] default = ["packets"] -packets = ["connecting", "dep:azalea-core"] +packets = ["connecting", "dep:azalea-core", "crc32"] +crc32 = ["crc32fast"] diff --git a/azalea-protocol/azalea-protocol-macros/src/lib.rs b/azalea-protocol/azalea-protocol-macros/src/lib.rs index a33d21e0..a1255519 100755 --- a/azalea-protocol/azalea-protocol-macros/src/lib.rs +++ b/azalea-protocol/azalea-protocol-macros/src/lib.rs @@ -9,13 +9,16 @@ use syn::{ fn as_packet_derive(input: TokenStream, state: proc_macro2::TokenStream) -> TokenStream { let DeriveInput { ident, data, .. } = parse_macro_input!(input); + // technically it would still work with enums and non-named structs but for + // consistency in the api it's nicer if they are all just structs, which is why + // we enforce this here let syn::Data::Struct(syn::DataStruct { fields, .. }) = &data else { panic!("#[derive(*Packet)] can only be used on structs") }; - let (syn::Fields::Named(_) | syn::Fields::Unit) = fields else { panic!("#[derive(*Packet)] can only be used on structs with named fields") }; + let variant_name = variant_name_from(&ident); let contents = quote! { diff --git a/azalea-protocol/src/packets/game/s_container_click.rs b/azalea-protocol/src/packets/game/s_container_click.rs index ed68de6e..b7b70888 100755 --- a/azalea-protocol/src/packets/game/s_container_click.rs +++ b/azalea-protocol/src/packets/game/s_container_click.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use azalea_buf::AzBuf; +use azalea_buf::{AzBuf, AzaleaWrite}; use azalea_inventory::{ItemStack, operations::ClickType}; use azalea_protocol_macros::ServerboundGamePacket; @@ -13,6 +13,70 @@ pub struct ServerboundContainerClick { pub slot_num: i16, pub button_num: u8, pub click_type: ClickType, - pub changed_slots: HashMap, + pub changed_slots: HashMap, pub carried_item: ItemStack, } + +/// Similar to an [`ItemStack`] but only carrying a CRC32 hash of the value of +/// added data components instead of their entire contents. +#[derive(Clone, Debug, AzBuf)] +pub struct HashedStack(pub Option); + +#[derive(Clone, Debug, AzBuf)] +pub struct HashedActualItem { + pub kind: azalea_registry::Item, + #[var] + pub count: i32, + pub components: HashedPatchMap, +} + +#[derive(Clone, Debug, AzBuf)] +pub struct HashedPatchMap { + /// The value is a CRC32 hash of the data component's network serialization. + /// (kind + data) + #[limit(256)] + pub added_components: Vec<(azalea_registry::DataComponentKind, u32)>, + #[limit(256)] + pub removed_components: Vec, +} + +/// Convert your [`ItemStack`] into a [`HashedStack`] by hashing the data +/// components. +/// +/// This will be necessary if you're writing a client or server, but if you're +/// just making a proxy then you can remove the `crc32` dependency by disabling +/// the `crc32` feature on `azalea-protocol`. +#[cfg(feature = "crc32")] +impl From<&ItemStack> for HashedStack { + fn from(item: &ItemStack) -> Self { + let ItemStack::Present(item) = item else { + return Self(None); + }; + + let mut added_components = Vec::new(); + let mut removed_components = Vec::new(); + + for (&kind, data) in &item.components.components { + if let Some(data) = data { + // encodeCap in TypedDataComponent.java + let mut buf = Vec::new(); + kind.azalea_write(&mut buf).unwrap(); + data.encode(&mut buf).unwrap(); + added_components.push((kind, crc32fast::hash(&buf))); + } else { + removed_components.push(kind); + } + } + + let components = HashedPatchMap { + added_components, + removed_components, + }; + let item = HashedActualItem { + kind: item.kind, + count: item.count, + components, + }; + Self(Some(item)) + } +}