diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index cc49f3785..bfcfb23d1 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -151,6 +151,10 @@ public class Item { if (repairCost != null) { builder.putInt("RepairCost", repairCost); } + + // Prevents the client from trying to stack items with untranslated components + // Relies on correct hash code implementation, and some luck + builder.putInt("GeyserHash", components.hashCode()); // TODO: don't rely on this } /** diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java index 7459ccf30..36752e582 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/PlayerInventoryTranslator.java @@ -261,6 +261,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator { GeyserItemStack sourceItem = inventory.getItem(sourceSlot); if (playerInv.getCursor().isEmpty()) { playerInv.setCursor(sourceItem.copy(0), session); + } else if (!InventoryUtils.canStack(sourceItem, playerInv.getCursor())) { + return rejectRequest(request); } playerInv.getCursor().add(transferAmount); @@ -272,6 +274,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator { GeyserItemStack sourceItem = playerInv.getCursor(); if (inventory.getItem(destSlot).isEmpty()) { inventory.setItem(destSlot, sourceItem.copy(0), session); + } else if (!InventoryUtils.canStack(sourceItem, inventory.getItem(destSlot))) { + return rejectRequest(request); } inventory.getItem(destSlot).add(transferAmount); @@ -284,6 +288,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator { GeyserItemStack sourceItem = inventory.getItem(sourceSlot); if (inventory.getItem(destSlot).isEmpty()) { inventory.setItem(destSlot, sourceItem.copy(0), session); + } else if (!InventoryUtils.canStack(sourceItem, inventory.getItem(destSlot))) { + return rejectRequest(request); } inventory.getItem(destSlot).add(transferAmount); diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 63644d5fc..64c95afdd 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -178,11 +178,26 @@ public class InventoryUtils { } public static boolean canStack(GeyserItemStack item1, GeyserItemStack item2) { + if (GeyserImpl.getInstance().getConfig().isDebugMode()) + canStackDebug(item1, item2); if (item1.isEmpty() || item2.isEmpty()) return false; return item1.getJavaId() == item2.getJavaId() && Objects.equals(item1.getComponents(), item2.getComponents()); } + private static void canStackDebug(GeyserItemStack item1, GeyserItemStack item2) { + DataComponents components1 = item1.getComponents(); + DataComponents components2 = item2.getComponents(); + if (components1 != null && components2 != null) { + if (components1.hashCode() == components2.hashCode() && !components1.equals(components2)) { + GeyserImpl.getInstance().getLogger().error("DEBUG: DataComponents hash collision"); + GeyserImpl.getInstance().getLogger().error("hash: " + components1.hashCode()); + GeyserImpl.getInstance().getLogger().error("components1: " + components1); + GeyserImpl.getInstance().getLogger().error("components2: " + components2); + } + } + } + /** * Checks to see if an item stack represents air or has no count. */