mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-27 15:00:13 +01:00
Prevent sending oversized item data in equipment and metadata
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
This commit is contained in:
parent
ce6fd58a5e
commit
c6f962ba54
7 changed files with 210 additions and 20 deletions
|
@ -0,0 +1,24 @@
|
|||
--- a/net/minecraft/core/component/DataComponents.java
|
||||
+++ b/net/minecraft/core/component/DataComponents.java
|
||||
@@ -180,10 +180,10 @@
|
||||
"map_post_processing", builder -> builder.networkSynchronized(MapPostProcessing.STREAM_CODEC)
|
||||
);
|
||||
public static final DataComponentType<ChargedProjectiles> CHARGED_PROJECTILES = register(
|
||||
- "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(ChargedProjectiles.STREAM_CODEC).cacheEncoding()
|
||||
+ "charged_projectiles", builder -> builder.persistent(ChargedProjectiles.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.CHARGED_PROJECTILES).cacheEncoding() // Paper - sanitize charged projectiles
|
||||
);
|
||||
public static final DataComponentType<BundleContents> BUNDLE_CONTENTS = register(
|
||||
- "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(BundleContents.STREAM_CODEC).cacheEncoding()
|
||||
+ "bundle_contents", builder -> builder.persistent(BundleContents.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.BUNDLE_CONTENTS).cacheEncoding() // Paper - sanitize bundle contents
|
||||
);
|
||||
public static final DataComponentType<PotionContents> POTION_CONTENTS = register(
|
||||
"potion_contents", builder -> builder.persistent(PotionContents.CODEC).networkSynchronized(PotionContents.STREAM_CODEC).cacheEncoding()
|
||||
@@ -250,7 +250,7 @@
|
||||
"pot_decorations", builder -> builder.persistent(PotDecorations.CODEC).networkSynchronized(PotDecorations.STREAM_CODEC).cacheEncoding()
|
||||
);
|
||||
public static final DataComponentType<ItemContainerContents> CONTAINER = register(
|
||||
- "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(ItemContainerContents.STREAM_CODEC).cacheEncoding()
|
||||
+ "container", builder -> builder.persistent(ItemContainerContents.CODEC).networkSynchronized(io.papermc.paper.util.DataSanitizationUtil.CONTAINER).cacheEncoding() // Paper - sanitize container contents
|
||||
);
|
||||
public static final DataComponentType<BlockItemStateProperties> BLOCK_STATE = register(
|
||||
"block_state", builder -> builder.persistent(BlockItemStateProperties.CODEC).networkSynchronized(BlockItemStateProperties.STREAM_CODEC).cacheEncoding()
|
|
@ -0,0 +1,14 @@
|
|||
--- a/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java
|
||||
+++ b/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java
|
||||
@@ -19,9 +19,11 @@
|
||||
}
|
||||
|
||||
private static void pack(List<SynchedEntityData.DataValue<?>> trackedValues, RegistryFriendlyByteBuf buf) {
|
||||
+ try (var ignored = io.papermc.paper.util.DataSanitizationUtil.start(true)) { // Paper - data sanitization
|
||||
for (SynchedEntityData.DataValue<?> dataValue : trackedValues) {
|
||||
dataValue.write(buf);
|
||||
}
|
||||
+ } // Paper - data sanitization
|
||||
|
||||
buf.writeByte(255);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
--- a/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java
|
||||
+++ b/net/minecraft/network/protocol/game/ClientboundSetEquipmentPacket.java
|
||||
@@ -19,6 +19,13 @@
|
||||
private final List<Pair<EquipmentSlot, ItemStack>> slots;
|
||||
|
||||
public ClientboundSetEquipmentPacket(int entityId, List<Pair<EquipmentSlot, ItemStack>> equipmentList) {
|
||||
+ // Paper start - data sanitization
|
||||
+ this(entityId, equipmentList, false);
|
||||
+ }
|
||||
+ private boolean sanitize;
|
||||
+ public ClientboundSetEquipmentPacket(int entityId, List<Pair<EquipmentSlot, ItemStack>> equipmentList, boolean sanitize) {
|
||||
+ this.sanitize = sanitize;
|
||||
+ // Paper end - data sanitization
|
||||
this.entity = entityId;
|
||||
this.slots = equipmentList;
|
||||
}
|
||||
@@ -40,6 +47,7 @@
|
||||
buf.writeVarInt(this.entity);
|
||||
int i = this.slots.size();
|
||||
|
||||
+ try (var ignored = io.papermc.paper.util.DataSanitizationUtil.start(this.sanitize)) { // Paper - data sanitization
|
||||
for (int j = 0; j < i; j++) {
|
||||
Pair<EquipmentSlot, ItemStack> pair = this.slots.get(j);
|
||||
EquipmentSlot equipmentSlot = pair.getFirst();
|
||||
@@ -48,6 +56,7 @@
|
||||
buf.writeByte(bl ? k | -128 : k);
|
||||
ItemStack.OPTIONAL_STREAM_CODEC.encode(buf, pair.getSecond());
|
||||
}
|
||||
+ } // Paper - data sanitization
|
||||
}
|
||||
|
||||
@Override
|
|
@ -154,9 +154,12 @@
|
|||
if (!collection.isEmpty()) {
|
||||
sender.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection));
|
||||
}
|
||||
@@ -344,6 +390,7 @@
|
||||
@@ -342,8 +388,9 @@
|
||||
}
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
|
||||
- sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
|
||||
+ sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization
|
||||
}
|
||||
+ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
|
||||
}
|
||||
|
|
|
@ -1913,7 +1913,7 @@
|
|||
+ entity.refreshEntityData(ServerGamePacketListenerImpl.this.player);
|
||||
+ // SPIGOT-7136 - Allays
|
||||
+ if (entity instanceof Allay || entity instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync
|
||||
+ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList())));
|
||||
+ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()), true)); // Paper - sanitize
|
||||
+ }
|
||||
+
|
||||
+ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory
|
||||
|
|
|
@ -495,7 +495,7 @@
|
|||
return flag;
|
||||
}
|
||||
}
|
||||
@@ -1031,13 +1234,39 @@
|
||||
@@ -1031,14 +1234,40 @@
|
||||
return this.getType().is(EntityTypeTags.INVERTED_HEALING_AND_HARM);
|
||||
}
|
||||
|
||||
|
@ -530,13 +530,14 @@
|
|||
- MobEffectInstance mobeffect = this.removeEffectNoUpdate(effect);
|
||||
+ return this.removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
|
||||
+ }
|
||||
+
|
||||
|
||||
+ public boolean removeEffect(Holder<MobEffect> holder, EntityPotionEffectEvent.Cause cause) {
|
||||
+ MobEffectInstance mobeffect = this.removeEffectNoUpdate(holder, cause);
|
||||
+ // CraftBukkit end
|
||||
|
||||
+
|
||||
if (mobeffect != null) {
|
||||
this.onEffectsRemoved(List.of(mobeffect));
|
||||
return true;
|
||||
@@ -1142,20 +1371,65 @@
|
||||
|
||||
}
|
||||
|
@ -1420,7 +1421,7 @@
|
|||
if (this.tickCount % 20 == 0) {
|
||||
this.getCombatTracker().recheckStatus();
|
||||
}
|
||||
@@ -2687,38 +3310,16 @@
|
||||
@@ -2687,37 +3310,15 @@
|
||||
gameprofilerfiller.pop();
|
||||
gameprofilerfiller.push("rangeChecks");
|
||||
|
||||
|
@ -1455,16 +1456,15 @@
|
|||
- while (this.yHeadRot - this.yHeadRotO < -180.0F) {
|
||||
- this.yHeadRotO -= 360.0F;
|
||||
- }
|
||||
+ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
|
||||
+ // Paper end
|
||||
|
||||
-
|
||||
- while (this.yHeadRot - this.yHeadRotO >= 180.0F) {
|
||||
- this.yHeadRotO += 360.0F;
|
||||
- }
|
||||
-
|
||||
+ this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
|
||||
+ // Paper end
|
||||
|
||||
gameprofilerfiller.pop();
|
||||
this.animStep += f2;
|
||||
if (this.isFallFlying()) {
|
||||
@@ -2741,7 +3342,7 @@
|
||||
this.elytraAnimationState.tick();
|
||||
}
|
||||
|
@ -1494,6 +1494,15 @@
|
|||
if (map == null) {
|
||||
map = Maps.newEnumMap(EquipmentSlot.class);
|
||||
}
|
||||
@@ -2864,7 +3472,7 @@
|
||||
}
|
||||
|
||||
});
|
||||
- ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list));
|
||||
+ ((ServerLevel) this.level()).getChunkSource().broadcast(this, new ClientboundSetEquipmentPacket(this.getId(), list, true)); // Paper - data sanitization
|
||||
}
|
||||
|
||||
private ItemStack getLastArmorItem(EquipmentSlot slot) {
|
||||
@@ -2974,8 +3582,10 @@
|
||||
} else if (this.isInLava() && (!this.onGround() || d3 > d4)) {
|
||||
this.jumpInLiquid(FluidTags.LAVA);
|
||||
|
@ -1715,7 +1724,7 @@
|
|||
+ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand);
|
||||
+ event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand); // Paper
|
||||
+ this.level().getCraftServer().getPluginManager().callEvent(event);
|
||||
+
|
||||
|
||||
+ if (event.isCancelled()) {
|
||||
+ // Update client
|
||||
+ Consumable consumable = this.useItem.get(DataComponents.CONSUMABLE);
|
||||
|
@ -1739,7 +1748,7 @@
|
|||
+ }
|
||||
+ // Paper end
|
||||
+ // CraftBukkit end
|
||||
|
||||
+
|
||||
if (itemstack != this.useItem) {
|
||||
this.setItemInHand(enumhand, itemstack);
|
||||
}
|
||||
|
@ -1770,8 +1779,8 @@
|
|||
} else {
|
||||
return null;
|
||||
}
|
||||
+ }
|
||||
+
|
||||
}
|
||||
|
||||
+ // Paper start - Make shield blocking delay configurable
|
||||
+ public HitResult getRayTrace(int maxDistance, ClipContext.Fluid fluidCollisionOption) {
|
||||
+ if (maxDistance < 1 || maxDistance > 120) {
|
||||
|
@ -1822,8 +1831,8 @@
|
|||
+
|
||||
+ public int getShieldBlockingDelay() {
|
||||
+ return shieldBlockingDelay;
|
||||
}
|
||||
|
||||
+ }
|
||||
+
|
||||
+ public void setShieldBlockingDelay(int shieldBlockingDelay) {
|
||||
+ this.shieldBlockingDelay = shieldBlockingDelay;
|
||||
+ }
|
||||
|
@ -1865,7 +1874,7 @@
|
|||
+ this.setPos(d0, d6, d2);
|
||||
if (world.noCollision((Entity) this) && !world.containsAnyLiquid(this.getBoundingBox())) {
|
||||
flag1 = true;
|
||||
+ }
|
||||
}
|
||||
+ // now revert and call event if the teleport place is valid
|
||||
+ this.setPos(d3, d4, d5);
|
||||
+
|
||||
|
@ -1885,7 +1894,7 @@
|
|||
+ return Optional.empty();
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
+ }
|
||||
+ // CraftBukkit end
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package io.papermc.paper.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.UnaryOperator;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.component.BundleContents;
|
||||
import net.minecraft.world.item.component.ChargedProjectiles;
|
||||
import net.minecraft.world.item.component.ItemContainerContents;
|
||||
import org.apache.commons.lang3.math.Fraction;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class DataSanitizationUtil {
|
||||
|
||||
private static final ThreadLocal<DataSanitizer> DATA_SANITIZER = ThreadLocal.withInitial(DataSanitizer::new);
|
||||
|
||||
public static DataSanitizer start(final boolean sanitize) {
|
||||
final DataSanitizer sanitizer = DATA_SANITIZER.get();
|
||||
if (sanitize) {
|
||||
sanitizer.start();
|
||||
}
|
||||
return sanitizer;
|
||||
}
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ChargedProjectiles> CHARGED_PROJECTILES = codec(ChargedProjectiles.STREAM_CODEC, DataSanitizationUtil::sanitizeChargedProjectiles);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, BundleContents> BUNDLE_CONTENTS = codec(BundleContents.STREAM_CODEC, DataSanitizationUtil::sanitizeBundleContents);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ItemContainerContents> CONTAINER = codec(ItemContainerContents.STREAM_CODEC, contents -> ItemContainerContents.EMPTY);
|
||||
|
||||
private static ChargedProjectiles sanitizeChargedProjectiles(final ChargedProjectiles projectiles) {
|
||||
if (projectiles.isEmpty()) {
|
||||
return projectiles;
|
||||
}
|
||||
|
||||
return ChargedProjectiles.of(List.of(
|
||||
new ItemStack(projectiles.contains(Items.FIREWORK_ROCKET) ? Items.FIREWORK_ROCKET : Items.ARROW)
|
||||
));
|
||||
}
|
||||
|
||||
private static BundleContents sanitizeBundleContents(final BundleContents contents) {
|
||||
if (contents.isEmpty()) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
// Bundles change their texture based on their fullness.
|
||||
// A bundles content weight may be anywhere from 0 to, basically, infinity.
|
||||
// A weight of 1 is the usual maximum case
|
||||
int sizeUsed = Mth.mulAndTruncate(contents.weight(), 64);
|
||||
// Early out, *most* bundles should not be overfilled above a weight of one.
|
||||
if (sizeUsed <= 64) return new BundleContents(List.of(new ItemStack(Items.PAPER, Math.max(1, sizeUsed))));
|
||||
|
||||
final List<ItemStack> sanitizedRepresentation = new ObjectArrayList<>(sizeUsed / 64 + 1);
|
||||
while (sizeUsed > 0) {
|
||||
final int stackCount = Math.min(64, sizeUsed);
|
||||
sanitizedRepresentation.add(new ItemStack(Items.PAPER, stackCount));
|
||||
sizeUsed -= stackCount;
|
||||
}
|
||||
// Now we add a single fake item that uses the same amount of slots as all other items.
|
||||
// Ensure that potentially overstacked bundles are not represented by empty (count=0) itemstacks.
|
||||
return new BundleContents(sanitizedRepresentation);
|
||||
}
|
||||
|
||||
private static <B, A> StreamCodec<B, A> codec(final StreamCodec<B, A> delegate, final UnaryOperator<A> sanitizer) {
|
||||
return new DataSanitizationCodec<>(delegate, sanitizer);
|
||||
}
|
||||
|
||||
private record DataSanitizationCodec<B, A>(StreamCodec<B, A> delegate, UnaryOperator<A> sanitizer) implements StreamCodec<B, A> {
|
||||
|
||||
@Override
|
||||
public @NonNull A decode(final @NonNull B buf) {
|
||||
return this.delegate.decode(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final @NonNull B buf, final @NonNull A value) {
|
||||
if (!DATA_SANITIZER.get().value().get()) {
|
||||
this.delegate.encode(buf, value);
|
||||
} else {
|
||||
this.delegate.encode(buf, this.sanitizer.apply(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record DataSanitizer(AtomicBoolean value) implements AutoCloseable {
|
||||
|
||||
public DataSanitizer() {
|
||||
this(new AtomicBoolean(false));
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.value.compareAndSet(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.value.compareAndSet(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private DataSanitizationUtil() {
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue