From 892c292dc9c712332671966965b1f508d41471b4 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Wed, 1 Dec 2021 13:32:36 +0100 Subject: [PATCH] Fix itemmeta patch and move oversized fix into its own (#6980) --- ...de-unnecessary-itemmeta-from-clients.patch | 109 ------------------ ...oversized-item-data-in-equipment-and.patch | 86 ++++++++++++++ ...de-unnecessary-itemmeta-from-clients.patch | 96 +++++++++++++++ 3 files changed, 182 insertions(+), 109 deletions(-) delete mode 100644 patches/server/0817-Hide-unnecessary-itemmeta-from-clients.patch create mode 100644 patches/server/0817-Prevent-sending-oversized-item-data-in-equipment-and.patch create mode 100644 patches/server/0818-Hide-unnecessary-itemmeta-from-clients.patch diff --git a/patches/server/0817-Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/0817-Hide-unnecessary-itemmeta-from-clients.patch deleted file mode 100644 index 23631f437..000000000 --- a/patches/server/0817-Hide-unnecessary-itemmeta-from-clients.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa -Date: Tue, 3 Aug 2021 17:28:27 +0200 -Subject: [PATCH] Hide unnecessary itemmeta from clients - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -index fd5927247e23878fd8bf6ab62a2eb477d07f4cc3..19ee843fa118a3b1b2c6b5551e9c6af7e7fcca11 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -@@ -916,6 +916,13 @@ public class PaperWorldConfig { - behaviorTickRates = loadTickRates("behavior"); - } - -+ public boolean hideItemmetaFromClients = true; -+ public boolean hideDurabilityFromClients = false; -+ private void getHideItemmetaFromClients() { -+ hideItemmetaFromClients = getBoolean("anticheat.obfuscation.items.hide-itemmeta", hideItemmetaFromClients); -+ hideDurabilityFromClients = getBoolean("anticheat.obfuscation.items.hide-durability", hideDurabilityFromClients); -+ } -+ - private com.google.common.collect.Table loadTickRates(String type) { - log(" " + type + ":"); - com.google.common.collect.Table table = com.google.common.collect.HashBasedTable.create(); -diff --git a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -index 3eb6bf4258b1de4697f96c2011df493cf7414a0c..7b7fe3f47144aa92b335aa4cc55e7e897ac11fc8 100644 ---- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -+++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -@@ -127,6 +127,7 @@ public class EntityDataSerializers { - public static final EntityDataSerializer ITEM_STACK = new EntityDataSerializer() { - @Override - public void write(FriendlyByteBuf buf, ItemStack value) { -+ value = net.minecraft.world.entity.LivingEntity.stripMeta(value, null, false); // Paper - strip items to prevent oversized data - buf.writeItem(value); - } - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 62daf918d4ab00963041ca869ae718f14f2e3337..a7ab33d1a6c30ffde7bf2ab8588df4437197fb9a 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3047,13 +3047,67 @@ public abstract class LivingEntity extends Entity { - - } - -+ public static ItemStack stripMeta(ItemStack itemStack, @org.jetbrains.annotations.Nullable Level level, boolean copy) { -+ if ((itemStack.getCount() > 1 || itemStack.hasTag()) && !itemStack.isEmpty()) { -+ if (copy) itemStack = itemStack.copy(); -+ CompoundTag nbt = itemStack.getTag(); -+ if (level != null && level.paperConfig.hideDurabilityFromClients) { -+ // Only show damage values for elytra's, since they show a different texture when broken. -+ if (!itemStack.is(Items.ELYTRA) || itemStack.getDamageValue() < itemStack.getMaxDamage() - 1) { -+ itemStack.setDamageValue(0); -+ } -+ } -+ -+ if (level != null && level.paperConfig.hideItemmetaFromClients) { -+ // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage, -+ // we'll tell the client if there's one or (more than) two items. -+ itemStack.setCount(itemStack.getCount() > 1 ? 2 : 1); -+ // We can't just strip out display, leather helmets still use the display.color tag. -+ if (nbt != null) { -+ if (nbt.get("display") instanceof CompoundTag displayTag) { -+ displayTag.remove("Lore"); -+ displayTag.remove("Name"); -+ } -+ -+ if (nbt.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) { -+ // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child. -+ ListTag enchantments = new ListTag(); -+ enchantments.add(new CompoundTag()); -+ nbt.put("Enchantments", enchantments); -+ } -+ nbt.remove("AttributeModifiers"); -+ } -+ } -+ // Always remove items to prevent oversized data -+ // Bundles change their texture based on their fullness. -+ if (nbt != null) { -+ if (itemStack.is(Items.BUNDLE) && nbt.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) { -+ org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) itemStack.asBukkitMirror().getItemMeta(); -+ int sizeUsed = 0; -+ for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) { -+ int scale = 64 / item.getMaxStackSize(); -+ sizeUsed += scale * item.getAmount(); -+ } -+ // Now we add a single fake item that uses the same amount of slots as all other items. -+ ListTag items = new ListTag(); -+ items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag())); -+ nbt.put("Items", items); -+ } -+ nbt.remove("BlockEntityTag"); -+ nbt.remove("EntityTag"); -+ } -+ } -+ return itemStack; -+ } -+ - private void handleEquipmentChanges(Map equipmentChanges) { - List> list = Lists.newArrayListWithCapacity(equipmentChanges.size()); - - equipmentChanges.forEach((enumitemslot, itemstack) -> { - ItemStack itemstack1 = itemstack.copy(); -+ final ItemStack toSend = stripMeta(itemstack1, this.level, true); // Paper - hide unnecessary itemmeta from clients - -- list.add(Pair.of(enumitemslot, itemstack1)); -+ list.add(Pair.of(enumitemslot, toSend)); // Paper - hide unnecessary itemmeta from clients - switch (enumitemslot.getType()) { - case HAND: - this.setLastHandItem(enumitemslot, itemstack1); diff --git a/patches/server/0817-Prevent-sending-oversized-item-data-in-equipment-and.patch b/patches/server/0817-Prevent-sending-oversized-item-data-in-equipment-and.patch new file mode 100644 index 000000000..a9b32c1a3 --- /dev/null +++ b/patches/server/0817-Prevent-sending-oversized-item-data-in-equipment-and.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Wed, 1 Dec 2021 12:36:25 +0100 +Subject: [PATCH] Prevent sending oversized item data in equipment and metadata + + +diff --git a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +index 3eb6bf4258b1de4697f96c2011df493cf7414a0c..bbf4e6b0ca0fe046469c675fc9e0929b64006548 100644 +--- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java ++++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +@@ -127,7 +127,7 @@ public class EntityDataSerializers { + public static final EntityDataSerializer ITEM_STACK = new EntityDataSerializer() { + @Override + public void write(FriendlyByteBuf buf, ItemStack value) { +- buf.writeItem(value); ++ buf.writeItem(net.minecraft.world.entity.LivingEntity.sanitizeItemStack(value, false)); // Paper - prevent oversized data + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 703ac671b19636859648f16a5431b2700791e7d5..d8ef6137133716b9ee519e6e4668d2e1ae5d9ca3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -319,7 +319,10 @@ public class ServerEntity { + ItemStack itemstack = ((LivingEntity) this.entity).getItemBySlot(enumitemslot); + + if (!itemstack.isEmpty()) { +- list.add(Pair.of(enumitemslot, itemstack.copy())); ++ // Paper start - prevent oversized data ++ final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); ++ list.add(Pair.of(enumitemslot, sanitized)); ++ // Paper end + } + } + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 62daf918d4ab00963041ca869ae718f14f2e3337..0b8b959e9d51ada4779f88ceceda8c072b083cd5 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3053,7 +3053,10 @@ public abstract class LivingEntity extends Entity { + equipmentChanges.forEach((enumitemslot, itemstack) -> { + ItemStack itemstack1 = itemstack.copy(); + +- list.add(Pair.of(enumitemslot, itemstack1)); ++ // Paper start - prevent oversized data ++ ItemStack toSend = sanitizeItemStack(itemstack1, true); ++ list.add(Pair.of(enumitemslot, toSend)); ++ // Paper end + switch (enumitemslot.getType()) { + case HAND: + this.setLastHandItem(enumitemslot, itemstack1); +@@ -3066,6 +3069,34 @@ public abstract class LivingEntity extends Entity { + ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); + } + ++ // Paper end - prevent oversized data ++ public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { ++ if (itemStack.isEmpty() || !itemStack.hasTag()) { ++ return itemStack; ++ } ++ ++ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; ++ final CompoundTag tag = copy.getTag(); ++ if (copy.is(Items.BUNDLE) && tag.get("Items") instanceof ListTag oldItems && !oldItems.isEmpty()) { ++ // Bundles change their texture based on their fullness. ++ org.bukkit.inventory.meta.BundleMeta bundleMeta = (org.bukkit.inventory.meta.BundleMeta) copy.asBukkitMirror().getItemMeta(); ++ int sizeUsed = 0; ++ for (org.bukkit.inventory.ItemStack item : bundleMeta.getItems()) { ++ int scale = 64 / item.getMaxStackSize(); ++ sizeUsed += scale * item.getAmount(); ++ } ++ // Now we add a single fake item that uses the same amount of slots as all other items. ++ ListTag items = new ListTag(); ++ items.add(new ItemStack(Items.PAPER, sizeUsed).save(new CompoundTag())); ++ tag.put("Items", items); ++ } ++ if (tag.get("BlockEntityTag") instanceof CompoundTag blockEntityTag) { ++ blockEntityTag.remove("Items"); ++ } ++ return copy; ++ } ++ // Paper end ++ + private ItemStack getLastArmorItem(EquipmentSlot slot) { + return (ItemStack) this.lastArmorItemStacks.get(slot.getIndex()); + } diff --git a/patches/server/0818-Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/0818-Hide-unnecessary-itemmeta-from-clients.patch new file mode 100644 index 000000000..930da9494 --- /dev/null +++ b/patches/server/0818-Hide-unnecessary-itemmeta-from-clients.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Noah van der Aa +Date: Tue, 3 Aug 2021 17:28:27 +0200 +Subject: [PATCH] Hide unnecessary itemmeta from clients + + +diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +index fd5927247e23878fd8bf6ab62a2eb477d07f4cc3..0544f03b65696fe9e972283254f7e51a98586072 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +@@ -916,6 +916,13 @@ public class PaperWorldConfig { + behaviorTickRates = loadTickRates("behavior"); + } + ++ public boolean hideItemmetaFromClients = false; ++ public boolean hideDurabilityFromClients = false; ++ private void getHideItemmetaFromClients() { ++ hideItemmetaFromClients = getBoolean("anticheat.obfuscation.items.hide-itemmeta", hideItemmetaFromClients); ++ hideDurabilityFromClients = getBoolean("anticheat.obfuscation.items.hide-durability", hideDurabilityFromClients); ++ } ++ + private com.google.common.collect.Table loadTickRates(String type) { + log(" " + type + ":"); + com.google.common.collect.Table table = com.google.common.collect.HashBasedTable.create(); +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index d8ef6137133716b9ee519e6e4668d2e1ae5d9ca3..9a6c67b614944f841813ec2892381c3342bc365c 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -321,7 +321,7 @@ public class ServerEntity { + if (!itemstack.isEmpty()) { + // Paper start - prevent oversized data + final ItemStack sanitized = LivingEntity.sanitizeItemStack(itemstack.copy(), false); +- list.add(Pair.of(enumitemslot, sanitized)); ++ list.add(Pair.of(enumitemslot, ((LivingEntity) this.entity).stripMeta(sanitized, false))); // Paper - remove unnecessary item meta + // Paper end + } + } +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 0b8b959e9d51ada4779f88ceceda8c072b083cd5..93b616d1b95e9c112dcd4b6fda9bd467177e6dec 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -3055,7 +3055,7 @@ public abstract class LivingEntity extends Entity { + + // Paper start - prevent oversized data + ItemStack toSend = sanitizeItemStack(itemstack1, true); +- list.add(Pair.of(enumitemslot, toSend)); ++ list.add(Pair.of(enumitemslot, stripMeta(toSend, toSend == itemstack1))); // Paper - hide unnecessary item meta + // Paper end + switch (enumitemslot.getType()) { + case HAND: +@@ -3069,6 +3069,45 @@ public abstract class LivingEntity extends Entity { + ((ServerLevel) this.level).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list)); + } + ++ // Paper start - hide unnecessary item meta ++ public ItemStack stripMeta(final ItemStack itemStack, final boolean copyItemStack) { ++ if (itemStack.isEmpty() || (!itemStack.hasTag() && itemStack.getCount() < 2)) { ++ return itemStack; ++ } ++ ++ final ItemStack copy = copyItemStack ? itemStack.copy() : itemStack; ++ if (level.paperConfig.hideDurabilityFromClients) { ++ // Only show damage values for elytra's, since they show a different texture when broken. ++ if (!copy.is(Items.ELYTRA) || copy.getDamageValue() < copy.getMaxDamage() - 1) { ++ copy.setDamageValue(0); ++ } ++ } ++ ++ if (level.paperConfig.hideItemmetaFromClients) { ++ // Some resource packs show different textures when there is more than one item. Since this shouldn't provide a big advantage, ++ // we'll tell the client if there's one or (more than) two items. ++ copy.setCount(copy.getCount() > 1 ? 2 : 1); ++ // We can't just strip out display, leather helmets still use the display.color tag. ++ final CompoundTag tag = copy.getTag(); ++ if (tag != null) { ++ if (tag.get("display") instanceof CompoundTag displayTag) { ++ displayTag.remove("Lore"); ++ displayTag.remove("Name"); ++ } ++ ++ if (tag.get("Enchantments") instanceof ListTag enchantmentsTag && !enchantmentsTag.isEmpty()) { ++ // The client still renders items with the enchantment glow if the enchantments tag contains at least one (empty) child. ++ ListTag enchantments = new ListTag(); ++ enchantments.add(new CompoundTag()); ++ tag.put("Enchantments", enchantments); ++ } ++ tag.remove("AttributeModifiers"); ++ } ++ } ++ return copy; ++ } ++ // Paper end ++ + // Paper end - prevent oversized data + public static ItemStack sanitizeItemStack(final ItemStack itemStack, final boolean copyItemStack) { + if (itemStack.isEmpty() || !itemStack.hasTag()) {