aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--azalea-client/src/plugins/inventory.rs15
-rw-r--r--azalea-inventory/src/slot.rs3
-rw-r--r--azalea-protocol/Cargo.toml4
-rwxr-xr-xazalea-protocol/azalea-protocol-macros/src/lib.rs5
-rwxr-xr-xazalea-protocol/src/packets/game/s_container_click.rs68
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<u16, ItemStack> = HashMap::new();
+ let mut changed_slots: HashMap<u16, HashedStack> = 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<DataComponentKind, Option<Box<dyn components::EncodableDataComponent>>>,
+ pub components:
+ IndexMap<DataComponentKind, Option<Box<dyn components::EncodableDataComponent>>>,
}
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<u16, ItemStack>,
+ pub changed_slots: HashMap<u16, HashedStack>,
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<HashedActualItem>);
+
+#[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<azalea_registry::DataComponentKind>,
+}
+
+/// 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))
+ }
+}