From f0468e04d99c1acd7b2004cab5919b4fa275cce5 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Wed, 5 Jun 2024 22:36:24 -0500 Subject: [PATCH] Fix data watchers and particles --- .../com/comphenix/protocol/PacketType.java | 5 +- .../com/comphenix/protocol/ProtocolLib.java | 18 +- .../protocol/utility/MinecraftReflection.java | 43 ++++- .../protocol/wrappers/EnumWrappers.java | 13 +- .../protocol/wrappers/WrappedDataWatcher.java | 161 +++++++++++++++--- .../protocol/wrappers/WrappedParticle.java | 4 +- 6 files changed, 195 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/comphenix/protocol/PacketType.java b/src/main/java/com/comphenix/protocol/PacketType.java index e690dc97..ed4d6c0b 100644 --- a/src/main/java/com/comphenix/protocol/PacketType.java +++ b/src/main/java/com/comphenix/protocol/PacketType.java @@ -26,6 +26,7 @@ import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Preconditions; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Iterables; + /** * Represents the type of a packet in a specific protocol. *

@@ -477,8 +478,8 @@ public class PacketType implements Serializable, Cloneable, Comparable getOrInferMinecraftClass(String className, Supplier> supplier) { + return getOptionalNMS(className).orElseGet(() -> { + Class clazz = supplier.get(); + return setMinecraftClass(className, clazz); + }); + } + /** * Retrieves the entity use action class in 1.17. * * @return The EntityUseAction class */ public static Class getEnumEntityUseActionClass() { - Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); - FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true); - try { - return fuzzyReflection.getFieldByType("^.*(EnumEntityUseAction)").getType(); - } catch (IllegalArgumentException ignored) { - return fuzzyReflection.getFieldByType("^.*(Action)").getType(); - } + return getOrInferMinecraftClass("ServerboundInteractPacket.Action", () -> { + Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); + FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true); + + Field field = fuzzyReflection.getField(FuzzyFieldContract.newBuilder() + .banModifier(Modifier.STATIC) + .typeDerivedOf(Object.class) + .build()); + if (field != null) { + return field.getType(); + } + + try { + return fuzzyReflection.getFieldByType("^.*(EnumEntityUseAction)").getType(); + } catch (IllegalArgumentException ignored) { + return fuzzyReflection.getFieldByType("^.*(Action)").getType(); + } + }); } /** @@ -1763,4 +1788,8 @@ public final class MinecraftReflection { public static Optional> getRegistryFriendlyByteBufClass() { return getOptionalNMS("network.RegistryFriendlyByteBuf"); } + + public static boolean isMojangMapped() { + return isMojangMapped; + } } diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index 889304d7..f9cab38f 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -7,12 +7,15 @@ import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.ExactReflection; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import org.apache.commons.lang.Validate; import org.bukkit.GameMode; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -549,8 +552,14 @@ public abstract class EnumWrappers { // In 1.17 the hand and use action class is no longer a field in the packet if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand"); - // class is named 'b' in the packet but class order differs in spigot and paper so we can only use the first method's return type (safest way) - ENTITY_USE_ACTION_CLASS = MinecraftReflection.getEnumEntityUseActionClass().getMethods()[0].getReturnType(); + + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true); + Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .parameterCount(0) + .returnTypeMatches(FuzzyMatchers.except(Void.class)) + .build()); + + ENTITY_USE_ACTION_CLASS = getType.getReturnType(); } else { HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1); ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0); diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 8757749a..325aff25 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -18,8 +18,11 @@ package com.comphenix.protocol.wrappers; import java.lang.reflect.*; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; @@ -30,6 +33,7 @@ import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.collection.ConvertedMap; import com.google.common.base.Optional; import com.google.common.collect.ImmutableBiMap; @@ -45,6 +49,11 @@ import org.bukkit.inventory.ItemStack; */ public class WrappedDataWatcher extends AbstractWrapper implements Iterable, ClonableWrapper { private static final Class HANDLE_TYPE = MinecraftReflection.getDataWatcherClass(); + private static final boolean ARRAY_BACKED = MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove(); + + private static Class SYNCHED_DATA_HOLDER_CLASS = ARRAY_BACKED + ? MinecraftReflection.getMinecraftClass("network.syncher.SyncedDataHolder") + : MinecraftReflection.getEntityClass(); private static MethodAccessor GETTER = null; private static MethodAccessor SETTER = null; @@ -53,6 +62,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable()); } /** @@ -89,8 +100,9 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable objects) { - this(); - - if (MinecraftReflection.watcherObjectExists()) { - for (WrappedWatchableObject object : objects) { - setObject(object.getWatcherObject(), object); - } - } else { - for (WrappedWatchableObject object : objects) { - setObject(object.getIndex(), object); - } - } + this(newHandle(fakeEntity(), objects)); } - private static Object newHandle(Object entity) { - if (constructor == null) { - constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass()); + private static final EquivalentConverter> ITEMS_CONVERTER = new EquivalentConverter>() { + @Override + public Object getGeneric(List specific) { + Object[] generic = (Object[]) Array.newInstance(MinecraftReflection.getDataWatcherItemClass(), specific.size()); + for (int i = 0; i < specific.size(); i++) { + generic[i] = specific.get(i).getHandle(); + } + return generic; } - return constructor.invoke(entity); + @Override + public List getSpecific(Object generic) { + Object[] genericArray = (Object[]) generic; + List specific = new ArrayList<>(genericArray.length); + for (Object genericObj : genericArray) { + specific.add(new WrappedWatchableObject(genericObj)); + } + return specific; + } + + @Override + @SuppressWarnings("unchecked") + public Class> getSpecificType() { + Class dummy = List.class; + return (Class>) dummy; + } + }; + + public static WrappedDataWatcher create(Entity entity, List objects) { + return new WrappedDataWatcher(newHandle(entity, objects)); + } + + private static Object newHandle(Object entity, List objects) { + if (constructor == null) { + constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, SYNCHED_DATA_HOLDER_CLASS, + MinecraftReflection.getArrayClass(MinecraftReflection.getDataWatcherItemClass())); + } + + Object[] genericItems = (Object[]) ITEMS_CONVERTER.getGeneric(objects); + return constructor.invoke(entity, genericItems); } private static Object fakeEntity() { @@ -142,6 +179,7 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable getMap() { if (MAP_FIELD == null) { try { @@ -152,19 +190,49 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable) MAP_FIELD.get(handle); } + private Object[] getBackingArray() { + if (ARRAY_FIELD == null) { + try { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(handleType, true); + ARRAY_FIELD = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract + .newBuilder() + .banModifier(Modifier.STATIC) + .typeDerivedOf(Object[].class) + .build())); + } catch (IllegalArgumentException ex) { + throw new FieldAccessException("Failed to find watchable object array", ex); + } + } + + Object[] backing = (Object[]) ARRAY_FIELD.get(handle); + return backing; + } + /** * Gets the contents of this DataWatcher as a map. * @return The contents */ + @Deprecated public Map asMap() { - return new ConvertedMap(getMap()) { + Map backingMap; + if (ARRAY_BACKED) { + Object[] backingArray = getBackingArray(); + backingMap = new HashMap<>(backingArray.length); + for (int i = 0; i < backingArray.length; i++) { + backingMap.put(i, backingArray[i]); + } + } else { + backingMap = getMap(); + } + + return new ConvertedMap(backingMap) { @Override protected WrappedWatchableObject toOuter(Object inner) { return inner != null ? new WrappedWatchableObject(inner) : null; @@ -181,7 +249,12 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable getIndexes() { + if (ARRAY_BACKED) { + return IntStream.range(0, size()).boxed().collect(Collectors.toSet()); + } + return getMap().keySet(); } @@ -189,7 +262,12 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable getWatchableObjects() { + if (ARRAY_BACKED) { + return (List)ITEMS_CONVERTER.getSpecific(getBackingArray()); + } + return new ArrayList<>(asMap().values()); } @@ -203,6 +281,10 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable indexSet() { - return getMap().keySet(); + return getIndexes(); } /** * Clears the contents of this DataWatcher. The watcher will be empty after * this operation is called. */ + @Deprecated public void clear() { getMap().clear(); } @@ -523,6 +614,10 @@ public class WrappedDataWatcher extends AbstractWrapper implements Iterable { .newBuilder() .requireModifier(Modifier.STATIC) .returnTypeExact(Particle.class) - .parameterExactArray(MinecraftReflection.getParticleClass()) + .parameterExactArray(MinecraftReflection.isMojangMapped() + ? MinecraftReflection.getParticleTypeClass() + : MinecraftReflection.getParticleClass()) .build(); toBukkit = Accessors.getMethodAccessor(fuzzy.getMethod(contract));