From 49eb39e45f009f8ba70ae50539cb20a900354f0d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 4 Jul 2013 01:12:39 +0200 Subject: [PATCH] Improve packet class lookup performance by mainaining a inverse map. --- .../protocol/injector/packet/InverseMaps.java | 130 ++++ .../injector/packet/PacketRegistry.java | 636 +++++++++--------- .../protocol/utility/MinecraftReflection.java | 2 +- 3 files changed, 464 insertions(+), 304 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java new file mode 100644 index 00000000..96f8ed5f --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java @@ -0,0 +1,130 @@ +package com.comphenix.protocol.injector.packet; + +import java.lang.reflect.Field; +import java.util.Map; + +import com.comphenix.protocol.reflect.FieldUtils; +import com.google.common.base.Predicate; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ForwardingMultimap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +public class InverseMaps { + private InverseMaps() { + // Not constructable + } + + public static Multimap inverseMultimap(final Map map, final Predicate> filter) { + final MapContainer container = new MapContainer(map); + + return new ForwardingMultimap() { + // The cached multimap + private Multimap inverseMultimap; + + @Override + protected Multimap delegate() { + if (container.hasChanged()) { + inverseMultimap = HashMultimap.create(); + + // Construct the inverse map + for (Map.Entry entry : map.entrySet()) { + if (filter.apply(entry)) { + inverseMultimap.put(entry.getValue(), entry.getKey()); + } + } + container.setChanged(false); + } + return inverseMultimap; + } + }; + } + + public static Map inverseMap(final Map map, final Predicate> filter) { + final MapContainer container = new MapContainer(map); + + return new ForwardingMap() { + // The cached map + private Map inverseMap; + + @Override + protected Map delegate() { + if (container.hasChanged()) { + inverseMap = Maps.newHashMap(); + + // Construct the inverse map + for (Map.Entry entry : map.entrySet()) { + if (filter.apply(entry)) { + inverseMap.put(entry.getValue(), entry.getKey()); + } + } + container.setChanged(false); + } + return inverseMap; + } + }; + } + + /** + * Represents a class that can detect if a map has changed. + * @author Kristian + */ + private static class MapContainer { + // For detecting changes + private Field modCountField; + private int lastModCount; + + // The object along with whether or not this is the initial run + private Object source; + private boolean changed; + + public MapContainer(Object source) { + this.source = source; + this.changed = true; + this.modCountField = FieldUtils.getField(source.getClass(), "modCount", true); + } + + /** + * Determine if the map has changed. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean hasChanged() { + // Check if unchanged + checkChanged(); + return changed; + } + + /** + * Mark the map as changed or unchanged. + * @param changed - TRUE if the map has changed, FALSE otherwise. + */ + public void setChanged(boolean changed) { + this.changed = changed; + } + + /** + * Check for modifications to the current map. + */ + protected void checkChanged() { + if (!changed) { + if (getModificationCount() != lastModCount) { + lastModCount = getModificationCount(); + changed = true; + } + } + } + + /** + * Retrieve the current modification count. + * @return The current count, or something different than lastModCount if not accessible. + */ + private int getModificationCount() { + try { + return modCountField != null ? modCountField.getInt(source) : lastModCount + 1; + } catch (Exception e) { + throw new RuntimeException("Unable to retrieve modCount.", e); + } + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index 72d70457..9448aff7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -1,303 +1,333 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.packet; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import net.sf.cglib.proxy.Factory; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.TroveWrapper; -import com.google.common.base.Objects; -import com.google.common.collect.ImmutableSet; - -/** - * Static packet registry in Minecraft. - * - * @author Kristian - */ -@SuppressWarnings("rawtypes") -public class PacketRegistry { - public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value."); - - public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s"); - public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s"); - - private static final int MIN_SERVER_PACKETS = 5; - private static final int MIN_CLIENT_PACKETS = 5; - - // Fuzzy reflection - private static FuzzyReflection packetRegistry; - - // The packet class to packet ID translator - private static Map packetToID; - - // Whether or not certain packets are sent by the client or the server - private static ImmutableSet serverPackets; - private static ImmutableSet clientPackets; - - // The underlying sets - private static Set serverPacketsRef; - private static Set clientPacketsRef; - - // New proxy values - private static Map overwrittenPackets = new HashMap(); - - // Vanilla packets - private static Map previousValues = new HashMap(); - - @SuppressWarnings({ "unchecked" }) - public static Map getPacketToID() { - // Initialize it, if we haven't already - if (packetToID == null) { - try { - Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); - packetToID = (Map) FieldUtils.readStaticField(packetsField, true); - } catch (IllegalArgumentException e) { - // Spigot 1.2.5 MCPC workaround - try { - packetToID = getSpigotWrapper(); - } catch (Exception e2) { - // Very bad indeed - throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2); - } - - } catch (IllegalAccessException e) { - throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); - } - } - - return packetToID; - } - - private static Map getSpigotWrapper() throws IllegalAccessException { - // If it talks like a duck, etc. - // Perhaps it would be nice to have a proper duck typing library as well - FuzzyClassContract mapLike = FuzzyClassContract.newBuilder(). - method(FuzzyMethodContract.newBuilder(). - nameExact("size").returnTypeExact(int.class)). - method(FuzzyMethodContract.newBuilder(). - nameExact("put").parameterCount(2)). - method(FuzzyMethodContract.newBuilder(). - nameExact("get").parameterCount(1)). - build(); - - Field packetsField = getPacketRegistry().getField( - FuzzyFieldContract.newBuilder().typeMatches(mapLike).build()); - Object troveMap = FieldUtils.readStaticField(packetsField, true); - - // Check for stupid no_entry_values - try { - Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true); - Integer value = (Integer) FieldUtils.readField(field, troveMap, true); - - if (value >= 0 && value < 256) { - // Someone forgot to set the no entry value. Let's help them. - FieldUtils.writeField(field, troveMap, -1); - } - } catch (IllegalArgumentException e) { - // Whatever - ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, - Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e)); - } - - // We'll assume this a Trove map - return TroveWrapper.getDecoratedMap(troveMap); - } - - /** - * Retrieve the cached fuzzy reflection instance allowing access to the packet registry. - * @return Reflected packet registry. - */ - private static FuzzyReflection getPacketRegistry() { - if (packetRegistry == null) - packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); - return packetRegistry; - } - - /** - * Retrieve the injected proxy classes handlig each packet ID. - * @return Injected classes. - */ - public static Map getOverwrittenPackets() { - return overwrittenPackets; - } - - /** - * Retrieve the vanilla classes handling each packet ID. - * @return Vanilla classes. - */ - public static Map getPreviousPackets() { - return previousValues; - } - - /** - * Retrieve every known and supported server packet. - * @return An immutable set of every known server packet. - * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. - */ - public static Set getServerPackets() throws FieldAccessException { - initializeSets(); - - // Sanity check. This is impossible! - if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS) - throw new FieldAccessException("Server packet list is empty. Seems to be unsupported"); - return serverPackets; - } - - /** - * Retrieve every known and supported client packet. - * @return An immutable set of every known client packet. - * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. - */ - public static Set getClientPackets() throws FieldAccessException { - initializeSets(); - - // As above - if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) - throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); - return clientPackets; - } - - @SuppressWarnings("unchecked") - private static void initializeSets() throws FieldAccessException { - if (serverPacketsRef == null || clientPacketsRef == null) { - List sets = getPacketRegistry().getFieldListByType(Set.class); - - try { - if (sets.size() > 1) { - serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true); - clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true); - - // Impossible - if (serverPacketsRef == null || clientPacketsRef == null) - throw new FieldAccessException("Packet sets are in an illegal state."); - - // NEVER allow callers to modify the underlying sets - serverPackets = ImmutableSet.copyOf(serverPacketsRef); - clientPackets = ImmutableSet.copyOf(clientPacketsRef); - - // Check sizes - if (serverPackets.size() < MIN_SERVER_PACKETS) - ProtocolLibrary.getErrorReporter().reportWarning( - PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size()) - ); - if (clientPackets.size() < MIN_CLIENT_PACKETS) - ProtocolLibrary.getErrorReporter().reportWarning( - PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size()) - ); - - } else { - throw new FieldAccessException("Cannot retrieve packet client/server sets."); - } - - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot access field.", e); - } - - } else { - // Copy over again if it has changed - if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size()) - serverPackets = ImmutableSet.copyOf(serverPacketsRef); - if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) - clientPackets = ImmutableSet.copyOf(clientPacketsRef); - } - } - - /** - * Retrieves the correct packet class from a given packet ID. - * @param packetID - the packet ID. - * @return The associated class. - */ - public static Class getPacketClassFromID(int packetID) { - return getPacketClassFromID(packetID, false); - } - - /** - * Retrieves the correct packet class from a given packet ID. - * @param packetID - the packet ID. - * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. - * @return The associated class. - */ - public static Class getPacketClassFromID(int packetID, boolean forceVanilla) { - - Map lookup = forceVanilla ? previousValues : overwrittenPackets; - - // Optimized lookup - if (lookup.containsKey(packetID)) { - return removeEnhancer(lookup.get(packetID), forceVanilla); - } - - // Will most likely not be used - for (Map.Entry entry : getPacketToID().entrySet()) { - if (Objects.equal(entry.getValue(), packetID)) { - // Attempt to get the vanilla class here too - if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey())) - return removeEnhancer(entry.getKey(), forceVanilla); - } - } - - throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); - } - - /** - * Retrieve the packet ID of a given packet. - * @param packet - the type of packet to check. - * @return The ID of the given packet. - * @throws IllegalArgumentException If this is not a valid packet. - */ - public static int getPacketID(Class packet) { - if (packet == null) - throw new IllegalArgumentException("Packet type class cannot be NULL."); - if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet)) - throw new IllegalArgumentException("Type must be a packet."); - - // The registry contains both the overridden and original packets - return getPacketToID().get(packet); - } - - /** - * Find the first superclass that is not a CBLib proxy object. - * @param clazz - the class whose hierachy we're going to search through. - * @param remove - whether or not to skip enhanced (proxy) classes. - * @return If remove is TRUE, the first superclass that is not a proxy. - */ - private static Class removeEnhancer(Class clazz, boolean remove) { - if (remove) { - // Get the underlying vanilla class - while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) { - clazz = clazz.getSuperclass(); - } - } - - return clazz; - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.packet; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +import net.sf.cglib.proxy.Factory; + +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.TroveWrapper; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +/** + * Static packet registry in Minecraft. + * + * @author Kristian + */ +@SuppressWarnings("rawtypes") +public class PacketRegistry { + public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value."); + + public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s"); + public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s"); + + private static final int MIN_SERVER_PACKETS = 5; + private static final int MIN_CLIENT_PACKETS = 5; + + // Fuzzy reflection + private static FuzzyReflection packetRegistry; + + // The packet class to packet ID translator + private static Map packetToID; + + // Packet IDs to classes, grouped by whether or not they're vanilla or custom defined + private static Multimap customIdToPacket; + private static Map vanillaIdToPacket; + + // Whether or not certain packets are sent by the client or the server + private static ImmutableSet serverPackets; + private static ImmutableSet clientPackets; + + // The underlying sets + private static Set serverPacketsRef; + private static Set clientPacketsRef; + + // New proxy values + private static Map overwrittenPackets = new HashMap(); + + // Vanilla packets + private static Map previousValues = new HashMap(); + + @SuppressWarnings({ "unchecked" }) + public static Map getPacketToID() { + // Initialize it, if we haven't already + if (packetToID == null) { + try { + Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); + packetToID = (Map) FieldUtils.readStaticField(packetsField, true); + } catch (IllegalArgumentException e) { + // Spigot 1.2.5 MCPC workaround + try { + packetToID = getSpigotWrapper(); + } catch (Exception e2) { + // Very bad indeed + throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2); + } + + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); + } + + // Create the inverse maps + customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate>() { + @Override + public boolean apply(@Nullable Entry entry) { + return !MinecraftReflection.isMinecraftClass(entry.getKey()); + } + }); + + // And the vanilla pack - here we assume a unique ID to class mapping + vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate>() { + @Override + public boolean apply(@Nullable Entry entry) { + return MinecraftReflection.isMinecraftClass(entry.getKey()); + } + }); + } + return packetToID; + } + + private static Map getSpigotWrapper() throws IllegalAccessException { + // If it talks like a duck, etc. + // Perhaps it would be nice to have a proper duck typing library as well + FuzzyClassContract mapLike = FuzzyClassContract.newBuilder(). + method(FuzzyMethodContract.newBuilder(). + nameExact("size").returnTypeExact(int.class)). + method(FuzzyMethodContract.newBuilder(). + nameExact("put").parameterCount(2)). + method(FuzzyMethodContract.newBuilder(). + nameExact("get").parameterCount(1)). + build(); + + Field packetsField = getPacketRegistry().getField( + FuzzyFieldContract.newBuilder().typeMatches(mapLike).build()); + Object troveMap = FieldUtils.readStaticField(packetsField, true); + + // Check for stupid no_entry_values + try { + Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true); + Integer value = (Integer) FieldUtils.readField(field, troveMap, true); + + if (value >= 0 && value < 256) { + // Someone forgot to set the no entry value. Let's help them. + FieldUtils.writeField(field, troveMap, -1); + } + } catch (IllegalArgumentException e) { + // Whatever + ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, + Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e)); + } + + // We'll assume this a Trove map + return TroveWrapper.getDecoratedMap(troveMap); + } + + /** + * Retrieve the cached fuzzy reflection instance allowing access to the packet registry. + * @return Reflected packet registry. + */ + private static FuzzyReflection getPacketRegistry() { + if (packetRegistry == null) + packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); + return packetRegistry; + } + + /** + * Retrieve the injected proxy classes handlig each packet ID. + * @return Injected classes. + */ + public static Map getOverwrittenPackets() { + return overwrittenPackets; + } + + /** + * Retrieve the vanilla classes handling each packet ID. + * @return Vanilla classes. + */ + public static Map getPreviousPackets() { + return previousValues; + } + + /** + * Retrieve every known and supported server packet. + * @return An immutable set of every known server packet. + * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. + */ + public static Set getServerPackets() throws FieldAccessException { + initializeSets(); + + // Sanity check. This is impossible! + if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS) + throw new FieldAccessException("Server packet list is empty. Seems to be unsupported"); + return serverPackets; + } + + /** + * Retrieve every known and supported client packet. + * @return An immutable set of every known client packet. + * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. + */ + public static Set getClientPackets() throws FieldAccessException { + initializeSets(); + + // As above + if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) + throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); + return clientPackets; + } + + @SuppressWarnings("unchecked") + private static void initializeSets() throws FieldAccessException { + if (serverPacketsRef == null || clientPacketsRef == null) { + List sets = getPacketRegistry().getFieldListByType(Set.class); + + try { + if (sets.size() > 1) { + serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true); + clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true); + + // Impossible + if (serverPacketsRef == null || clientPacketsRef == null) + throw new FieldAccessException("Packet sets are in an illegal state."); + + // NEVER allow callers to modify the underlying sets + serverPackets = ImmutableSet.copyOf(serverPacketsRef); + clientPackets = ImmutableSet.copyOf(clientPacketsRef); + + // Check sizes + if (serverPackets.size() < MIN_SERVER_PACKETS) + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size()) + ); + if (clientPackets.size() < MIN_CLIENT_PACKETS) + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size()) + ); + + } else { + throw new FieldAccessException("Cannot retrieve packet client/server sets."); + } + + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot access field.", e); + } + + } else { + // Copy over again if it has changed + if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size()) + serverPackets = ImmutableSet.copyOf(serverPacketsRef); + if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) + clientPackets = ImmutableSet.copyOf(clientPacketsRef); + } + } + + /** + * Retrieves the correct packet class from a given packet ID. + * @param packetID - the packet ID. + * @return The associated class. + */ + public static Class getPacketClassFromID(int packetID) { + return getPacketClassFromID(packetID, false); + } + + /** + * Retrieves the correct packet class from a given packet ID. + * @param packetID - the packet ID. + * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. + * @return The associated class. + */ + public static Class getPacketClassFromID(int packetID, boolean forceVanilla) { + Map lookup = forceVanilla ? previousValues : overwrittenPackets; + Class result = null; + + // Optimized lookup + if (lookup.containsKey(packetID)) { + return removeEnhancer(lookup.get(packetID), forceVanilla); + } + + // Refresh lookup tables + getPacketToID(); + + // See if we can look for non-vanilla classes + if (!forceVanilla) { + result = Iterables.getFirst(customIdToPacket.get(packetID), null); + } + if (result == null) { + result = vanillaIdToPacket.get(packetID); + } + + // See if we got it + if (result != null) + return result; + else + throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); + } + + /** + * Retrieve the packet ID of a given packet. + * @param packet - the type of packet to check. + * @return The ID of the given packet. + * @throws IllegalArgumentException If this is not a valid packet. + */ + public static int getPacketID(Class packet) { + if (packet == null) + throw new IllegalArgumentException("Packet type class cannot be NULL."); + if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet)) + throw new IllegalArgumentException("Type must be a packet."); + + // The registry contains both the overridden and original packets + return getPacketToID().get(packet); + } + + /** + * Find the first superclass that is not a CBLib proxy object. + * @param clazz - the class whose hierachy we're going to search through. + * @param remove - whether or not to skip enhanced (proxy) classes. + * @return If remove is TRUE, the first superclass that is not a proxy. + */ + private static Class removeEnhancer(Class clazz, boolean remove) { + if (remove) { + // Get the underlying vanilla class + while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) { + clazz = clazz.getSuperclass(); + } + } + + return clazz; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 9261f7bb..cd6af1d2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -287,7 +287,7 @@ public class MinecraftReflection { public static boolean isMinecraftClass(@Nonnull Class clazz) { if (clazz == null) throw new IllegalArgumentException("Class cannot be NULL."); - + return getMinecraftObjectMatcher().isMatch(clazz, null); }