From 05122150077fc54ae986fa250ae00f4284db5858 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Tue, 4 Aug 2020 19:03:59 -0400 Subject: [PATCH] Add modifiers for world keys and moving block position Fixes #906 Fixes #927 --- .../protocol/events/PacketContainer.java | 50 ++++++--- .../protocol/reflect/StructureModifier.java | 27 +++-- .../protocol/wrappers/BukkitConverters.java | 58 +++++++++- .../wrappers/MovingObjectPositionBlock.java | 102 ++++++++++++++++++ .../protocol/events/PacketContainerTest.java | 24 ++++- 5 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java diff --git a/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 6c742a46..9aae9f14 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -29,10 +29,7 @@ import javax.annotation.Nullable; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.injector.StructureCache; -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.ObjectWriter; -import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.*; import com.comphenix.protocol.reflect.cloning.*; import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; @@ -148,6 +145,7 @@ public class PacketContainer implements Serializable { // TODO this is kinda hacky, come up with a better solution if (type == PacketType.Play.Server.CHAT) { + System.out.println("writing UUID to chat packet"); getUUIDs().writeSafely(0, new UUID(0L, 0L)); } } @@ -927,15 +925,18 @@ public class PacketContainer implements Serializable { * @return A modifier for dimension IDs */ public StructureModifier getDimensions() { - // this isn't technically correct (and is, therefore, an inferior type of correct) - // but the resource keys are parameterized and we might have to modify structure modifier to support it - // or at least come up with a way to reflectively obtain ResourceKey - // TODO a more complete solution - - return structureModifier.withType( - NEW_DIMENSIONS ? MinecraftReflection.getMinecraftClass("ResourceKey") : MinecraftReflection.getMinecraftClass("DimensionManager"), - BukkitConverters.getDimensionIDConverter() - ); + if (NEW_DIMENSIONS) { + return structureModifier.withType( + MinecraftReflection.getMinecraftClass("ResourceKey"), + BukkitConverters.getDimensionIDConverter(), + MinecraftReflection.getMinecraftClass("DimensionManager") + ); + } else { + return structureModifier.withType( + MinecraftReflection.getMinecraftClass("DimensionManager"), + BukkitConverters.getDimensionIDConverter() + ); + } } /** @@ -949,6 +950,29 @@ public class PacketContainer implements Serializable { )); } + /** + * Retrieve a read/write structure for MovingObjectPositionBlock in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier getMovingBlockPositions() { + return structureModifier.withType( + MovingObjectPositionBlock.getNmsClass(), + MovingObjectPositionBlock.getConverter() + ); + } + + /** + * Retrieve a read/write structure for World ResourceKeys in 1.16+ + * @return The Structure Modifier + */ + public StructureModifier getWorldKeys() { + return structureModifier.withType( + MinecraftReflection.getMinecraftClass("ResourceKey"), + BukkitConverters.getWorldKeyConverter(), + MinecraftReflection.getNmsWorldClass() + ); + } + /** * Retrieve a read/write structure for the Map class. * @param keyConverter Converter for map keys diff --git a/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index f8e5363f..e9eadd15 100644 --- a/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -19,6 +19,8 @@ package com.comphenix.protocol.reflect; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; @@ -458,7 +460,16 @@ public class StructureModifier { return this; } - + + private static Type[] getParamTypes(Field field) { + Type genericType = field.getGenericType(); + if (genericType instanceof ParameterizedType) { + return ((ParameterizedType) genericType).getActualTypeArguments(); + } + + return new Class[0]; + } + /** * Retrieves a structure modifier that only reads and writes fields of a given type. * @param Type @@ -467,7 +478,7 @@ public class StructureModifier { * @return A structure modifier for fields of this type. */ @SuppressWarnings("unchecked") - public StructureModifier withType(Class fieldType, EquivalentConverter converter) { + public StructureModifier withType(Class fieldType, EquivalentConverter converter, Class... paramTypes) { if (fieldType == null) { // It's not supported in this version, so return an empty modifier return new StructureModifier() { @@ -489,11 +500,13 @@ public class StructureModifier { for (Field field : data) { if (fieldType.isAssignableFrom(field.getType())) { - filtered.add(field); - - // Don't use the original index - if (defaultFields.containsKey(field)) - defaults.put(field, index); + if (paramTypes.length == 0 || Arrays.equals(getParamTypes(field), paramTypes)) { + filtered.add(field); + + // Don't use the original index + if (defaultFields.containsKey(field)) + defaults.put(field, index); + } } // Keep track of the field index diff --git a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index 81a88c3f..4677dff4 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -52,10 +52,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.WorldType; +import org.bukkit.*; import org.bukkit.advancement.Advancement; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; @@ -1171,6 +1168,59 @@ public class BukkitConverters { private static MethodAccessor dimensionFromId = null; private static MethodAccessor idFromDimension = null; + private static FieldAccessor worldKeyField = null; + private static MethodAccessor getServer = null; + private static MethodAccessor getWorldServer = null; + private static MethodAccessor getWorld = null; + + public static EquivalentConverter getWorldKeyConverter() { + return ignoreNull(new EquivalentConverter() { + @Override + public Object getGeneric(World specific) { + Object nmsWorld = getWorldConverter().getGeneric(specific); + + if (worldKeyField == null) { + Class worldClass = MinecraftReflection.getNmsWorldClass(); + Class resourceKeyClass = MinecraftReflection.getMinecraftClass("ResourceKey"); + + FuzzyReflection fuzzy = FuzzyReflection.fromClass(nmsWorld.getClass(), true); + worldKeyField = Accessors.getFieldAccessor(fuzzy.getParameterizedField(resourceKeyClass, worldClass)); + } + + return worldKeyField.get(nmsWorld); + } + + @Override + public World getSpecific(Object generic) { + if (getServer == null) { + getServer = Accessors.getMethodAccessor(Bukkit.getServer().getClass(), "getServer"); + } + + Object server = getServer.invoke(Bukkit.getServer()); + if (getWorldServer == null) { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(server.getClass(), false); + getWorldServer = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract + .newBuilder() + .parameterExactArray(generic.getClass()) + .returnTypeExact(MinecraftReflection.getWorldServerClass()) + .build())); + } + + Object worldServer = getWorldServer.invoke(server, generic); + if (getWorld == null) { + getWorld = Accessors.getMethodAccessor(worldServer.getClass(), "getWorld"); + } + + return (World) getWorld.invoke(worldServer); + } + + @Override + public Class getSpecificType() { + return World.class; + } + }); + } + public static EquivalentConverter getDimensionIDConverter() { return ignoreNull(new EquivalentConverter() { @Override diff --git a/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java b/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java new file mode 100644 index 00000000..93a631d4 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/MovingObjectPositionBlock.java @@ -0,0 +1,102 @@ +package com.comphenix.protocol.wrappers; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.EnumWrappers.Direction; + +import org.bukkit.util.Vector; + +public class MovingObjectPositionBlock implements Cloneable { + private static final Class NMS_CLASS = MinecraftReflection.getNullableNMS("MovingObjectPositionBlock"); + + private BlockPosition position; + private Vector posVector; + private Direction direction; + private boolean insideBlock; + + public MovingObjectPositionBlock() { } + + public MovingObjectPositionBlock(BlockPosition position, Vector posVector, Direction direction, boolean insideBlock) { + this.position = position; + this.posVector = posVector; + this.direction = direction; + this.insideBlock = insideBlock; + } + + public static Class getNmsClass() { + return NMS_CLASS; + } + + public BlockPosition getBlockPosition() { + return position; + } + + public void setBlockPosition(BlockPosition position) { + this.position = position; + } + + public Vector getPosVector() { + return posVector; + } + + public void setPosVector(Vector vector) { + this.posVector = vector; + } + + public Direction getDirection() { + return direction; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + public boolean isInsideBlock() { + return insideBlock; + } + + public void setInsideBlock(boolean insideBlock) { + this.insideBlock = insideBlock; + } + + private static ConstructorAccessor constructor; + + public static EquivalentConverter getConverter() { + return Converters.ignoreNull(new EquivalentConverter() { + @Override + public Object getGeneric(MovingObjectPositionBlock specific) { + if (constructor == null) { + constructor = Accessors.getConstructorAccessor(NMS_CLASS, + MinecraftReflection.getVec3DClass(), + EnumWrappers.getDirectionClass(), + MinecraftReflection.getBlockPositionClass(), + boolean.class); + } + + Object nmsVector = BukkitConverters.getVectorConverter().getGeneric(specific.posVector); + Object nmsDirection = EnumWrappers.getDirectionConverter().getGeneric(specific.direction); + Object nmsBlockPos = BlockPosition.getConverter().getGeneric(specific.position); + + return constructor.invoke(nmsVector, nmsDirection, nmsBlockPos, specific.insideBlock); + } + + @Override + public MovingObjectPositionBlock getSpecific(Object generic) { + StructureModifier modifier = new StructureModifier<>(generic.getClass()).withTarget(generic); + Direction direction = modifier.withType(EnumWrappers.getDirectionClass(), EnumWrappers.getDirectionConverter()).read(0); + BlockPosition blockPos = modifier.withType(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()).read(0); + Vector posVector = modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0); + boolean insideBlock = (boolean) modifier.withType(boolean.class).read(1); + return new MovingObjectPositionBlock(blockPos, posVector, direction, insideBlock); + } + + @Override + public Class getSpecificType() { + return MovingObjectPositionBlock.class; + } + }); + } +} diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index e95ebf9a..04b5368c 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -30,6 +30,7 @@ import com.comphenix.protocol.utility.Util; import com.comphenix.protocol.wrappers.BlockPosition; import com.comphenix.protocol.wrappers.*; import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory; +import com.comphenix.protocol.wrappers.MovingObjectPositionBlock; import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry; import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject; import com.comphenix.protocol.wrappers.nbt.NbtCompound; @@ -52,6 +53,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -501,7 +503,7 @@ public class PacketContainerTest { } @Test - public void testDimensionManager() { + public void testDimensions() { PacketContainer container = new PacketContainer(PacketType.Play.Server.RESPAWN); container.getDimensions().write(0, 1); assertEquals((Object) 1, container.getDimensions().read(0)); @@ -521,6 +523,26 @@ public class PacketContainerTest { assertEquals(data, written); } + @Test + public void testMovingBlockPos() { + PacketContainer container = new PacketContainer(PacketType.Play.Client.USE_ITEM); + + Vector vector = new Vector(0, 1, 2); + BlockPosition position = new BlockPosition(3, 4, 5); + EnumWrappers.Direction direction = EnumWrappers.Direction.DOWN; + + MovingObjectPositionBlock movingPos = new MovingObjectPositionBlock(position, vector, direction, true); + container.getMovingBlockPositions().write(0, movingPos); + + MovingObjectPositionBlock back = container.getMovingBlockPositions().read(0); + + assertEquals(back.getPosVector(), vector); + assertEquals(back.getBlockPosition(), position); + assertEquals(back.getDirection(), direction); + assertTrue(back.isInsideBlock()); + } + + /** * Actions from the outbound Boss packet. Used for testing generic enums. * @author dmulloy2