From a89a84e6dd9e9ae09dc2a236665cc64f7426aafd Mon Sep 17 00:00:00 2001 From: Nassim Jahnke <nassim@njahnke.dev> 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 | 141 ++++++++---------- ...oversized-item-data-in-equipment-and.patch | 86 +++++++++++ 2 files changed, 150 insertions(+), 77 deletions(-) create mode 100644 patches/server/Prevent-sending-oversized-item-data-in-equipment-and.patch diff --git a/patches/server/Hide-unnecessary-itemmeta-from-clients.patch b/patches/server/Hide-unnecessary-itemmeta-from-clients.patch index bd57826820..6d8bb93ff9 100644 --- a/patches/server/Hide-unnecessary-itemmeta-from-clients.patch +++ b/patches/server/Hide-unnecessary-itemmeta-from-clients.patch @@ -12,7 +12,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 behaviorTickRates = loadTickRates("behavior"); } -+ public boolean hideItemmetaFromClients = true; ++ public boolean hideItemmetaFromClients = false; + public boolean hideDurabilityFromClients = false; + private void getHideItemmetaFromClients() { + hideItemmetaFromClients = getBoolean("anticheat.obfuscation.items.hide-itemmeta", hideItemmetaFromClients); @@ -22,88 +22,75 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private com.google.common.collect.Table<String, String, Integer> loadTickRates(String type) { log(" " + type + ":"); com.google.common.collect.Table<String, String, Integer> 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 +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -+++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java -@@ -0,0 +0,0 @@ public class EntityDataSerializers { - public static final EntityDataSerializer<ItemStack> ITEM_STACK = new EntityDataSerializer<ItemStack>() { - @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); - } - +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -0,0 +0,0 @@ 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<EquipmentSlot, ItemStack> equipmentChanges) { - List<Pair<EquipmentSlot, ItemStack>> 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 + // 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: - this.setLastHandItem(enumitemslot, itemstack1); +@@ -0,0 +0,0 @@ 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()) { diff --git a/patches/server/Prevent-sending-oversized-item-data-in-equipment-and.patch b/patches/server/Prevent-sending-oversized-item-data-in-equipment-and.patch new file mode 100644 index 0000000000..d73ac30f37 --- /dev/null +++ b/patches/server/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 <nassim@njahnke.dev> +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java ++++ b/src/main/java/net/minecraft/network/syncher/EntityDataSerializers.java +@@ -0,0 +0,0 @@ public class EntityDataSerializers { + public static final EntityDataSerializer<ItemStack> ITEM_STACK = new EntityDataSerializer<ItemStack>() { + @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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -0,0 +0,0 @@ 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); +@@ -0,0 +0,0 @@ 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()); + }