From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Cryptite Date: Wed, 6 Oct 2021 11:03:01 -0500 Subject: [PATCH] Packet obfuscation and reduction Minecraft is overzealous about packet updates for Entities. In Loka's case, we want to reduce as many unnecessary packet updates as possible. This patch is likely to be updated over and over in terms of reducing packet sends. In summary, this patch creates the concept of a "foreignValue" of a packet's data. We treat packets in two ways: 1) The packet sent to the player itself (the normal way). This always has all of the values as usual. 2) The packet data as seen by any other (foreign) players. This patch adds the ability to set a "foreignValue" for an entity value so as to obfuscate data received by other players. The current packets modified/obfuscated are the following: # Health - Foreign Players will only receive packets that say the player is "alive or dead (max health or 0 health). # This reduces the amount of health packet updates as well which is great for players in combat. # Air Level - Foreign players will only ever see a player as having full oxygen # Air level packets are sent PER-TICK, and as such a player with any change in air level will only spam themselves # with packets instead of every single player within tracking distance # Score - Foreign players will only see a player's score as 0 and will not update due to gathering xp orbs, etc. diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java index 3e17f6131bf590d7c4a16b79c1c145cb4f565bc9..e1233fa58d068448d0accef7a7f6725fcb902848 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetEntityDataPacket.java @@ -22,6 +22,13 @@ public class ClientboundSetEntityDataPacket implements Packet> packedItems) { + this.id = id; + this.packedItems = packedItems; + } + // Slice end + public ClientboundSetEntityDataPacket(FriendlyByteBuf buf) { this.id = buf.readVarInt(); this.packedItems = SynchedEntityData.unpack(buf); diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java index 4df12454001f0de5f358c88d876e34c35a736c42..72c74d2369a36b14f1103aa74b096f50e7990f4d 100644 --- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java @@ -136,6 +136,11 @@ public class SynchedEntityData { } public void set(EntityDataAccessor key, T value) { + //Slice start + set(key, value, null); + } + + public void set(EntityDataAccessor key, T value, @Nullable T foreignValue) { // Slice end SynchedEntityData.DataItem datawatcher_item = this.getItem(key); if (ObjectUtils.notEqual(value, datawatcher_item.getValue())) { @@ -145,6 +150,11 @@ public class SynchedEntityData { this.isDirty = true; } + // Slice start + if (foreignValue != null && ObjectUtils.notEqual(foreignValue, datawatcher_item.getForeignValue())) { + datawatcher_item.setForeignValue(foreignValue); + } + // Slice end } // CraftBukkit start - add method from above @@ -200,6 +210,28 @@ public class SynchedEntityData { return list; } + // Slice start + @Nullable + public List> packForeignDirty(List> unpackedData) { + List> list = null; + + for (DataItem dataItem : unpackedData) { + DataItem item = itemsById.get(dataItem.accessor.getId()); + if (item.isDirty(true)) { + item.setForeignDirty(false); + + if (list == null) { + list = Lists.newArrayList(); + } + + list.add(item.copy(true)); + } + } + + return list; + } + // Slice end + @Nullable public List> getAll() { List> list = null; @@ -313,11 +345,14 @@ public class SynchedEntityData { final EntityDataAccessor accessor; T value; private boolean dirty; + @Nullable T foreignValue = null; // Slice + private boolean foreignDirty; // Slice public DataItem(EntityDataAccessor data, T value) { this.accessor = data; this.value = value; this.dirty = true; + this.foreignDirty = true; // Slice } public EntityDataAccessor getAccessor() { @@ -343,5 +378,34 @@ public class SynchedEntityData { public SynchedEntityData.DataItem copy() { return new SynchedEntityData.DataItem<>(this.accessor, this.accessor.getSerializer().copy(this.value)); } + + // Slice start + public void setForeignValue(T foreignValue) { + this.foreignValue = foreignValue; + this.foreignDirty = true; + } + + public @Nullable T getForeignValue() { + return foreignValue; + } + + public boolean isDirty(boolean foreign) { + if (foreign) { + //There must be a foreign value in order for this to be dirty, otherwise we consider this a normal + //value and check the normal dirty flag. + return foreignValue == null || this.foreignDirty; + } + + return this.dirty; + } + + public void setForeignDirty(boolean dirty) { + this.foreignDirty = dirty; + } + + public SynchedEntityData.DataItem copy(boolean foreign) { + return new SynchedEntityData.DataItem<>(this.accessor, this.accessor.getSerializer().copy((foreign && this.foreignValue != null ? this.foreignValue : this.value))); + } + // Slice end } } diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index b7c9294fdd3d799d410afba4a1118aa371c98533..a6dcec3660b17feae7c3c1eeb7717097c71241bf 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -368,7 +368,19 @@ public class ServerEntity { SynchedEntityData datawatcher = this.entity.getEntityData(); if (datawatcher.isDirty()) { - this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), datawatcher, false)); + // Slice start + ClientboundSetEntityDataPacket dataPacket = new ClientboundSetEntityDataPacket(this.entity.getId(), datawatcher, false); + if (this.entity instanceof ServerPlayer serverPlayer) { + serverPlayer.connection.send(dataPacket); + } + + //Get the packedData that the original packet has, and then determine if any of those are changed in + //the foreign version. If null, nothing to notify foreign trackers about. + List> dirtyItems = datawatcher.packForeignDirty(dataPacket.getUnpackedData()); + if (dirtyItems != null) { + this.broadcast(new ClientboundSetEntityDataPacket(this.entity.getId(), dirtyItems)); + } + // Slice end } if (this.entity instanceof LivingEntity) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index c2f38dd9f30f803aaab71465352caa55802acdc1..01ea2e9a9aa4486d3960c3fc2f88256aea3e8f62 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -2944,7 +2944,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n if (event.isCancelled()) { return; } - this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount()); + this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount(), getMaxAirSupply()); // Slice // CraftBukkit end } diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java index 4a35056b01b947c57f7ca06c974463008fc4bbef..80a8ea35b47cc649f435856cad19a80d69da8425 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -633,7 +633,7 @@ public abstract class Player extends LivingEntity { public void increaseScore(int score) { int j = this.getScore(); - this.entityData.set(Player.DATA_SCORE_ID, j + score); + this.entityData.set(Player.DATA_SCORE_ID, j + score, 0); // Slice } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index c64911651f3d736c83cc83996de04920b091cc57..cf8d4edba400d5f21737c0af52a1193f69386fa9 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1997,7 +1997,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.sendHealthUpdate(); } } - this.getHandle().getEntityData().set(LivingEntity.DATA_HEALTH_ID, (float) this.getScaledHealth()); + this.getHandle().getEntityData().set(LivingEntity.DATA_HEALTH_ID, (float) this.getScaledHealth(), isDead() ? 0f : 20f); // Slice this.getHandle().maxHealthCache = getMaxHealth(); }