diff --git a/ProtocolLib/src/com/comphenix/protocol/Packets.java b/ProtocolLib/src/com/comphenix/protocol/Packets.java index 587d0fdd..6532150f 100644 --- a/ProtocolLib/src/com/comphenix/protocol/Packets.java +++ b/ProtocolLib/src/com/comphenix/protocol/Packets.java @@ -1,5 +1,9 @@ package com.comphenix.protocol; +import java.util.Set; + +import com.comphenix.protocol.injector.PacketFilterManager; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.IntEnum; /** @@ -92,6 +96,25 @@ public final class Packets { return INSTANCE; } + /** + * Determine if the given packet is a valid server packet in the current version of Minecraft. + * @param packetID - the packet to test. + * @return TRUE if this packet is supported, FALSE otherwise. + * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. + */ + public static boolean isSupported(int packetID) throws FieldAccessException { + return PacketFilterManager.getServerPackets().contains(packetID); + } + + /** + * Retrieve every client packet the current version of Minecraft is aware of. + * @return Every supported server packet. + * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. + */ + public static Set getSupported() throws FieldAccessException { + return PacketFilterManager.getServerPackets(); + } + // We only allow a single instance of this class private Server() { super(); @@ -146,6 +169,25 @@ public final class Packets { return INSTANCE; } + /** + * Determine if the given packet is a valid client packet in the current version of Minecraft. + * @param packetID - the packet to test. + * @return TRUE if this packet is supported, FALSE otherwise. + * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. + */ + public static boolean isSupported(int packetID) throws FieldAccessException { + return PacketFilterManager.getClientPackets().contains(packetID); + } + + /** + * Retrieve every client packet the current version of Minecraft is aware of. + * @return Every supported client packet. + * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. + */ + public static Set getSupported() throws FieldAccessException { + return PacketFilterManager.getClientPackets(); + } + // Like above private Client() { super(); diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java b/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java index 2e372e1e..34930aee 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/MinecraftRegistry.java @@ -19,13 +19,17 @@ package com.comphenix.protocol.injector; import java.lang.reflect.Field; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import net.minecraft.server.Packet; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; /** * Static registries in Minecraft. @@ -35,9 +39,16 @@ import com.google.common.base.Objects; @SuppressWarnings("rawtypes") class MinecraftRegistry { + // 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 Set serverPackets; + private static Set clientPackets; + // New proxy values private static Map overwrittenPackets = new HashMap(); @@ -49,7 +60,7 @@ class MinecraftRegistry { // Initialize it, if we haven't already if (packetToID == null) { try { - Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("packetsField", Map.class); + Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); packetToID = (Map) FieldUtils.readStaticField(packetsField, true); } catch (IllegalAccessException e) { @@ -60,14 +71,80 @@ class MinecraftRegistry { return packetToID; } + /** + * 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(Packet.class, 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(); + 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(); + return clientPackets; + } + + @SuppressWarnings("unchecked") + private static void initializeSets() throws FieldAccessException { + if (serverPackets == null || clientPackets == null) { + List sets = getPacketRegistry().getFieldListByType(Set.class); + + try { + if (sets.size() > 1) { + serverPackets = (Set) FieldUtils.readStaticField(sets.get(0), true); + clientPackets = (Set) FieldUtils.readStaticField(sets.get(1), true); + + // Impossible + if (serverPackets == null || clientPackets == null) + throw new FieldAccessException("Packet sets are in an illegal state."); + + // NEVER allow callers to modify the underlying sets + serverPackets = ImmutableSet.copyOf(serverPackets); + clientPackets = ImmutableSet.copyOf(clientPackets); + + } else { + throw new FieldAccessException("Cannot retrieve packet client/server sets."); + } + + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot access field.", e); + } + } + } + /** * Retrieves the correct packet class from a given packet ID. * @param packetID - the packet ID. diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java index 7a1c612d..fc9db4e0 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketFilterManager.java @@ -107,6 +107,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // The async packet handler private AsyncFilterManager asyncFilterManager; + // Valid server and client packets + private Set serverPackets; + private Set clientPackets; + + /** * Only create instances of this class if protocol lib is disabled. */ @@ -123,6 +128,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok this.playerInjection = new PlayerInjectionHandler(classLoader, logger, this, server); this.packetInjector = new PacketInjector(classLoader, this, playerInjection); this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); + + // Attempt to load the list of server and client packets + try { + this.serverPackets = MinecraftRegistry.getServerPackets(); + this.clientPackets = MinecraftRegistry.getClientPackets(); + } catch (FieldAccessException e) { + logger.log(Level.WARNING, "Cannot load server and client packet list.", e); + } + } catch (IllegalAccessException e) { logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); } @@ -180,7 +194,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (hasSending) { verifyWhitelist(listener, sending); sendingListeners.addListener(listener, sending); - enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist()); + enablePacketFilters(listener, ConnectionSide.SERVER_SIDE, sending.getWhitelist()); // Make sure this is possible playerInjection.checkListener(listener); @@ -188,7 +202,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (hasReceiving) { verifyWhitelist(listener, receiving); recievedListeners.addListener(listener, receiving); - enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); + enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); } // Inform our injected hooks @@ -304,18 +318,39 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok *

* Note that all packets are disabled by default. * + * @param listener - the listener that requested to enable these filters. * @param side - which side the event will arrive from. * @param packets - the packet id(s). */ - private void enablePacketFilters(ConnectionSide side, Iterable packets) { + private void enablePacketFilters(PacketListener listener, ConnectionSide side, Iterable packets) { if (side == null) throw new IllegalArgumentException("side cannot be NULL."); + + // Note the difference between unsupported and valid. + // Every packet ID between and including 0 - 255 is valid, but only a subset is supported. for (int packetID : packets) { - if (side.isForServer()) - playerInjection.addPacketHandler(packetID); - if (side.isForClient() && packetInjector != null) - packetInjector.addPacketHandler(packetID); + // Only register server packets that are actually supported by Minecraft + if (side.isForServer()) { + if (serverPackets != null && serverPackets.contains(packetID)) + playerInjection.addPacketHandler(packetID); + else + logger.warning(String.format( + "[%s] Unsupported server packet ID in current Minecraft version: %s", + PacketAdapter.getPluginName(listener), packetID + )); + } + + // As above, only for client packets + if (side.isForClient() && packetInjector != null) { + if (clientPackets != null && clientPackets.contains(packetID)) + packetInjector.addPacketHandler(packetID); + else + logger.warning(String.format( + "[%s] Unsupported client packet ID in current Minecraft version: %s", + PacketAdapter.getPluginName(listener), packetID + )); + } } } @@ -560,7 +595,25 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok e.printStackTrace(); } } - + + /** + * 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 { + return MinecraftRegistry.getServerPackets(); + } + + /** + * 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 { + return MinecraftRegistry.getClientPackets(); + } + /** * Retrieves the current plugin class loader. * @return Class loader. @@ -574,6 +627,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok return hasClosed; } + /** + * Called when ProtocolLib is closing. + */ public void close() { // Guard if (hasClosed) diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java index 9e042885..ee1e98de 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/PacketInjector.java @@ -117,6 +117,10 @@ class PacketInjector { Map registry = MinecraftRegistry.getPacketToID(); Class old = MinecraftRegistry.getPacketClassFromID(packetID); + // If this packet is not known + if (old == null) { + throw new IllegalStateException("Packet ID " + packetID + " is not a valid packet ID in this version."); + } // Check for previous injections if (!old.getName().startsWith("net.minecraft.")) { throw new IllegalStateException("Packet " + packetID + " has already been injected.");