From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Cryptite Date: Wed, 6 Oct 2021 11:03:01 -0500 Subject: [PATCH] (Slice) 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: # This reduces the amount of health packet updates as well which is great for players in combat. # 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 diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java index 0f99733660f91280e4c6262cf75b3c9cae86f65a..ba9f8fe6fafc54bbdfb104de28af4b392feb4483 100644 --- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java @@ -22,6 +22,7 @@ public class SynchedEntityData { private final SyncedDataHolder entity; private final SynchedEntityData.DataItem[] itemsById; private boolean isDirty; + private boolean isForeignDirty; // Slice SynchedEntityData(SyncedDataHolder trackedEntity, SynchedEntityData.DataItem[] entries) { this.entity = trackedEntity; @@ -63,6 +64,16 @@ public class SynchedEntityData { } public void set(EntityDataAccessor key, T value, boolean force) { + // Slice start + this.set(key, value, null, force); + } + + public void set(EntityDataAccessor key, T value, T foreignValue) { + this.set(key, value, foreignValue, false); + } + + public void set(EntityDataAccessor key, T value, T foreignValue, boolean force) { + // Slice end SynchedEntityData.DataItem datawatcher_item = this.getItem(key); if (force || ObjectUtils.notEqual(value, datawatcher_item.getValue())) { @@ -72,6 +83,12 @@ public class SynchedEntityData { this.isDirty = true; } + // Slice start + if (foreignValue != null && ObjectUtils.notEqual(foreignValue, datawatcher_item.getForeignValue())) { + datawatcher_item.setForeignValue(foreignValue); + this.isForeignDirty = true; + } + // Slice end } // CraftBukkit start - add method from above @@ -81,6 +98,12 @@ public class SynchedEntityData { } // CraftBukkit end + // Slice start + public boolean isForeignDirty() { + return this.isForeignDirty; + } + // Slice end + public boolean isDirty() { return this.isDirty; } @@ -108,6 +131,29 @@ public class SynchedEntityData { } } + // Slice start + @Nullable + public List> packForeignDirty(List> unpackedData) { + List> list = null; + + for (DataValue dataItem : unpackedData) { + DataItem item = this.itemsById[dataItem.id()]; + if (item.isDirty(true)) { + item.setForeignDirty(false); + + if (list == null) { + list = new ArrayList<>(); + } + + list.add(item.copy(true)); + } + } + + this.isForeignDirty = false; + return list; + } + // Slice end + @Nullable public List> getNonDefaultValues() { List> list = null; @@ -171,11 +217,14 @@ public class SynchedEntityData { T value; private final T initialValue; private boolean dirty; + @Nullable T foreignValue = null; // Slice + private boolean foreignDirty; // Slice public DataItem(EntityDataAccessor data, T value) { this.accessor = data; this.initialValue = value; this.value = value; + this.foreignDirty = true; // Slice } public EntityDataAccessor getAccessor() { @@ -202,6 +251,35 @@ public class SynchedEntityData { return this.initialValue.equals(this.value); } + // Slice start + public void setForeignValue(T foreignValue) { + this.foreignValue = foreignValue; + this.foreignDirty = true; + } + + public @Nullable T getForeignValue() { + return this.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 this.foreignValue == null || this.foreignDirty; + } + + return this.dirty; + } + + public void setForeignDirty(boolean dirty) { + this.foreignDirty = dirty; + } + + public SynchedEntityData.DataValue copy(boolean foreign) { + return SynchedEntityData.DataValue.create(this.accessor, this.accessor.serializer().copy((foreign && this.foreignValue != null ? this.foreignValue : this.value))); + } + // Slice end + public SynchedEntityData.DataValue value() { return SynchedEntityData.DataValue.create(this.accessor, this.value); } diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index 90eb4927fa51ce3df86aa7b6c71f49150a03e337..a960c7ac83cb32b2eb1f292fd9a561bae5677324 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -156,7 +156,7 @@ public class ServerEntity { } } - if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isDirty()) { // Paper - fix desync when a player is added to the tracker + if (this.forceStateResync || this.tickCount % this.updateInterval == 0 || this.entity.hasImpulse || this.entity.getEntityData().isForeignDirty()) { // Slice // Paper - fix desync when a player is added to the tracker byte b0 = Mth.packDegrees(this.entity.getYRot()); byte b1 = Mth.packDegrees(this.entity.getXRot()); boolean flag = Math.abs(b0 - this.lastSentYRot) >= 1 || Math.abs(b1 - this.lastSentXRot) >= 1; @@ -449,7 +449,15 @@ public class ServerEntity { if (list != null) { this.trackedDataValues = datawatcher.getNonDefaultValues(); - this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); + // Slice start + if (!(this.entity instanceof ServerPlayer)) { + list = datawatcher.packForeignDirty(list); + } + + if (list != null) { + this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list)); + } + // 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 d16ed3ea0ae380ddccb28a99062c7cccf84c4877..124916e1ee9d32a530cbce90ed22fc1383cf49d1 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -3717,7 +3717,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID); 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/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java index 6127993804a8c0b342d74c62f163b8371f7c049b..35d8422ea3bcdc52db6a7f6702fd593346b30c2c 100644 --- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java @@ -124,7 +124,7 @@ public class FallingBlockEntity extends Entity { } public void setStartPos(BlockPos pos) { - this.entityData.set(FallingBlockEntity.DATA_START_POS, pos); + this.entityData.set(FallingBlockEntity.DATA_START_POS, pos, BlockPos.ZERO); // Slice } public BlockPos getStartPos() { diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java index 2ddfea0dd64cf6a2295583b747ab195346c24530..25a25b5c6497c9823a41e6bd8fd22f0841377c62 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -217,7 +217,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { } public void setFuse(int fuse) { - this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse); + this.entityData.set(PrimedTnt.DATA_FUSE_ID, fuse, (fuse / 10) * 10); // Slice } public int getFuse() { 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 61d412c4f1ebd55661cc3f0260468e3ac0efe0bb..c3e90356ab409f408b767618db718e3926edf8d1 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -676,7 +676,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 } public void startAutoSpinAttack(int riptideTicks, float riptideAttackDamage, ItemStack stack) {