From c137640feea9063e22b788c8d8643400cf145ba6 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 4 Dec 2013 04:17:02 +0100 Subject: [PATCH] Updating ProtocolLib for 1.7.2. Still a work in progress. --- ProtocolLib/pom.xml | 2 +- .../protocol/CleanupStaticMembers.java | 1 + .../com/comphenix/protocol/PacketType.java | 433 ++++++++++++-- .../comphenix/protocol/PacketTypeLookup.java | 90 +++ .../comphenix/protocol/ProtocolLibrary.java | 39 +- .../protocol/collections/IntegerMap.java | 139 +++++ .../protocol/events/NetworkMarker.java | 10 + .../protocol/events/PacketContainer.java | 118 +++- .../protocol/events/PacketEvent.java | 14 +- .../protocol/injector/BukkitUnwrapper.java | 16 + .../protocol/injector/ListenerInvoker.java | 1 + .../protocol/injector/LoginPackets.java | 15 + .../protocol/injector/PacketConstructor.java | 53 +- .../injector/PacketFilterManager.java | 16 +- .../protocol/injector/StructureCache.java | 81 ++- .../injector/netty/BootstrapList.java | 92 +++ .../injector/netty/ChannelInjector.java | 538 ++++++++++++++++++ .../protocol/injector/netty/ChannelProxy.java | 57 ++ .../injector/netty/NettyProtocol.java | 28 - .../injector/netty/NettyProtocolInjector.java | 309 ++++++++++ .../injector/netty/NettyProtocolRegistry.java | 129 +++++ .../injector/netty/NettySocketAdaptor.java | 248 ++++++++ .../injector/packet/LegacyPacketRegistry.java | 344 +++++++++++ .../injector/packet/PacketInjector.java | 2 +- .../injector/packet/PacketRegistry.java | 490 +++++++++------- .../spigot/AbstractPacketInjector.java | 52 ++ .../spigot/AbstractPlayerHandler.java | 81 +++ .../injector/spigot/DummyPacketInjector.java | 46 +- .../injector/spigot/DummyPlayerHandler.java | 73 +-- .../protocol/reflect/FuzzyReflection.java | 171 +++++- .../protocol/reflect/ObjectEnum.java | 38 +- .../protocol/reflect/StructureModifier.java | 4 +- .../reflect/cloning/AggregateCloner.java | 2 +- .../reflect/cloning/ImmutableDetector.java | 12 +- .../protocol/utility/MinecraftFields.java | 56 ++ .../protocol/utility/MinecraftMethods.java | 168 ++++++ .../protocol/utility/MinecraftReflection.java | 75 ++- .../protocol/utility/MinecraftVersion.java | 5 +- .../protocol/utility/SnapshotVersion.java | 21 +- .../protocol/wrappers/ChunkPosition.java | 8 +- .../protocol/wrappers/WrappedDataWatcher.java | 87 ++- .../wrappers/collection/ConvertedMap.java | 48 +- .../protocol/wrappers/nbt/NameProperty.java | 94 +++ .../protocol/wrappers/nbt/NbtFactory.java | 82 ++- .../wrappers/nbt/WrappedCompound.java | 19 +- .../protocol/wrappers/nbt/WrappedElement.java | 47 +- .../protocol/wrappers/nbt/WrappedList.java | 12 +- .../wrappers/nbt/io/NbtBinarySerializer.java | 2 +- .../protocol/SimpleCraftBukkitITCase.java | 7 +- .../protocol/BukkitInitialization.java | 15 +- .../protocol/events/PacketContainerTest.java | 61 +- .../utility/StreamSerializerTest.java | 4 +- .../wrappers/WrappedAttributeTest.java | 8 +- .../protocol/wrappers/nbt/NbtFactoryTest.java | 2 +- 54 files changed, 3981 insertions(+), 584 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/collections/IntegerMap.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocol.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettySocketAdaptor.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/LegacyPacketRegistry.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NameProperty.java diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 1c69f4a4..410a3107 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -231,7 +231,7 @@ org.bukkit craftbukkit - 1.6.4-R2.0 + 1.7.2-R0.1-SNAPSHOT provided diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index 5688e8eb..b8f46183 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -70,6 +70,7 @@ class CleanupStaticMembers { */ public void resetAll() { // This list must always be updated + @SuppressWarnings("deprecation") Class[] publicClasses = { AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class, BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java index e543dce6..7da64a13 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketType.java @@ -1,8 +1,23 @@ package com.comphenix.protocol; import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +import org.bukkit.Bukkit; + +import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.ObjectEnum; +import com.comphenix.protocol.utility.MinecraftVersion; + import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; /** * Represents the type of a packet in a specific protocol. @@ -15,23 +30,66 @@ public class PacketType implements Serializable { // Increment whenever the type changes private static final long serialVersionUID = 1L; + /** + * Represents an unknown legacy packet ID. + */ + public static final int UNKNOWN_PACKET = -1; + + /** + * Packets sent during handshake. + * @author Kristian + */ public static class Handshake { - public static final Protocol PROTOCOL = Protocol.HANDSHAKE; + private static final Protocol PROTOCOL = Protocol.HANDSHAKING; public static class Client extends ObjectEnum { - public final static Sender SENDER = Sender.CLIENT; - public final static Client INSTANCE = new Client(); - + private final static Sender SENDER = Sender.CLIENT; public static final PacketType HANDSHAKE = new PacketType(PROTOCOL, SENDER, 0x00, 2); + + private final static Client INSTANCE = new Client(); + + // Prevent accidental construction + private Client() { super(PacketType.class); } + + public static Client getInstance() { + return INSTANCE; + } + public static Sender getSender() { + return SENDER; + } + } + + /** + * An empty enum, as the server will not send any packets in this protocol. + * @author Kristian + */ + public static class Server extends ObjectEnum { + private final static Sender SENDER = Sender.CLIENT; + private final static Server INSTANCE = new Server(); + private Server() { super(PacketType.class); } + + public static Server getInstance() { + return INSTANCE; + } + public static Sender getSender() { + return SENDER; + } + } + + public static Protocol getProtocol() { + return PROTOCOL; } } - public static class Game { - public static final Protocol PROTOCOL = Protocol.GAME; + /** + * Packets sent and recieved when logged into the game. + * @author Kristian + */ + public static class Play { + private static final Protocol PROTOCOL = Protocol.GAME; - public static class Server extends ObjectEnum { - public final static Sender SENDER = Sender.SERVER; - public final static Server INSTANCE = new Server(); + public static class Server extends ObjectEnum { + private final static Sender SENDER = Sender.SERVER; public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x00, 0); public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x01, 1); @@ -83,7 +141,7 @@ public class PacketType implements Serializable { public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, 103); public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x30, 104); public static final PacketType CRAFT_PROGRESS_BAR = new PacketType(PROTOCOL, SENDER, 0x31, 105); - public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x32, 106); + public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x32, 106); public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x33, 130); public static final PacketType ITEM_DATA = new PacketType(PROTOCOL, SENDER, 0x34, 131); public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x35, 132); @@ -99,11 +157,23 @@ public class PacketType implements Serializable { public static final PacketType SET_SCOREOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x3E, 209); public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x3F, 250); public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x40, 255); + + // The instance must + private final static Server INSTANCE = new Server(); + + // Prevent accidental construction + private Server() { super(PacketType.class); } + + public static Sender getSender() { + return SENDER; + } + public static Server getInstance() { + return INSTANCE; + } } public static class Client extends ObjectEnum { - public final static Sender SENDER = Sender.CLIENT; - public final static Client INSTANCE = new Client(); + private final static Sender SENDER = Sender.CLIENT; public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x00, 0); public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x01, 3); @@ -129,51 +199,127 @@ public class PacketType implements Serializable { public static final PacketType LOCALE_AND_VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x15, 204); public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x16, 205); public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x17, 250); + + private final static Client INSTANCE = new Client(); + + // Prevent accidental construction + private Client() { super(PacketType.class); } + + public static Sender getSender() { + return SENDER; + } + public static Client getInstance() { + return INSTANCE; + } + } + + public static Protocol getProtocol() { + return PROTOCOL; } } + /** + * Packets sent and recieved when querying the server in the multiplayer menu. + * @author Kristian + */ public static class Status { - public static final Protocol PROTOCOL = Protocol.STATUS; + private static final Protocol PROTOCOL = Protocol.STATUS; public static class Server extends ObjectEnum { - public final static Sender SENDER = Sender.SERVER; - public final static Server INSTANCE = new Server(); + private final static Sender SENDER = Sender.SERVER; public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, 255); @SuppressWarnings("deprecation") - public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Server.PING_TIME); + public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x01, Packets.Server.PING_TIME); + + private final static Server INSTANCE = new Server(); + + // Prevent accidental construction + private Server() { super(PacketType.class); } + + public static Sender getSender() { + return SENDER; + } + public static Server getInstance() { + return INSTANCE; + } } public static class Client extends ObjectEnum { - public final static Sender SENDER = Sender.CLIENT; - public final static Client INSTANCE = new Client(); + private final static Sender SENDER = Sender.CLIENT; public static final PacketType STATUS_REQUEST = new PacketType(PROTOCOL, SENDER, 0x00, 254); @SuppressWarnings("deprecation") - public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.PING_TIME); + public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x01, Packets.Client.PING_TIME); + + private final static Client INSTANCE = new Client(); + + // Prevent accidental construction + private Client() { super(PacketType.class); } + + public static Sender getSender() { + return SENDER; + } + public static Client getInstance() { + return INSTANCE; + } + } + + public static Protocol getProtocol() { + return PROTOCOL; } } + /** + * Packets sent and recieved when logging in to the server. + * @author Kristian + */ public static class Login { - public static final Protocol PROTOCOL = Protocol.LOGIN; + private static final Protocol PROTOCOL = Protocol.LOGIN; public static class Server extends ObjectEnum { - public final static Sender SENDER = Sender.SERVER; - public final static Server INSTANCE = new Server(); + private final static Sender SENDER = Sender.SERVER; public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, 255); public static final PacketType KEY_REQUEST = new PacketType(PROTOCOL, SENDER, 0x01, 253); @SuppressWarnings("deprecation") public static final PacketType LOGIN_SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, Packets.Server.LOGIN_SUCCESS); + + private final static Server INSTANCE = new Server(); + + // Prevent accidental construction + private Server() { super(PacketType.class); } + + public static Sender getSender() { + return SENDER; + } + public static Server getInstance() { + return INSTANCE; + } } public static class Client extends ObjectEnum { - public final static Sender SENDER = Sender.CLIENT; - public final static Client INSTANCE = new Client(); + private final static Sender SENDER = Sender.CLIENT; @SuppressWarnings("deprecation") - public static final PacketType LOGIN_START = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.LOGIN_START); + public static final PacketType LOGIN_START = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.LOGIN_START); public static final PacketType KEY_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x01, 252); + + private final static Client INSTANCE = new Client(); + + // Prevent accidental construction + private Client() { super(PacketType.class); } + + public static Sender getSender() { + return SENDER; + } + public static Client getInstance() { + return INSTANCE; + } + } + + public static Protocol getProtocol() { + return PROTOCOL; } } @@ -182,10 +328,29 @@ public class PacketType implements Serializable { * @author Kristian */ public enum Protocol { - HANDSHAKE, + HANDSHAKING, GAME, STATUS, - LOGIN + LOGIN; + + /** + * Retrieve the correct protocol enum from a given vanilla enum instance. + * @param vanilla - the vanilla protocol enum instance. + * @return The corresponding protocol. + */ + public static Protocol fromVanilla(Enum vanilla) { + String name = vanilla.name(); + + if ("HANDSHAKING".equals(name)) + return HANDSHAKING; + if ("PLAY".equals(name)) + return GAME; + if ("STATUS".equals(name)) + return STATUS; + if ("LOGIN".equals(name)) + return LOGIN; + throw new IllegalArgumentException("Unrecognized vanilla enum " + vanilla); + } } /** @@ -205,23 +370,189 @@ public class PacketType implements Serializable { SERVER } + // Lookup of packet types + private static PacketTypeLookup LOOKUP; + + /** + * Protocol version of all the current IDs. + */ + private static final MinecraftVersion PROTOCOL_VERSION = MinecraftVersion.WORLD_UPDATE; + private final Protocol protocol; private final Sender sender; private final int currentId; private final int legacyId; + private final MinecraftVersion version; + /** + * Retrieve the current packet/legacy lookup. + * @return The packet type lookup. + */ + private static PacketTypeLookup getLookup() { + if (LOOKUP == null) { + LOOKUP = new PacketTypeLookup(). + addPacketTypes(Handshake.Client.getInstance()). + addPacketTypes(Handshake.Server.getInstance()). + addPacketTypes(Play.Client.getInstance()). + addPacketTypes(Play.Server.getInstance()). + addPacketTypes(Status.Client.getInstance()). + addPacketTypes(Status.Server.getInstance()). + addPacketTypes(Login.Client.getInstance()). + addPacketTypes(Login.Server.getInstance()); + } + return LOOKUP; + } + + /** + * Find every packet type known to the current version of ProtocolLib. + * @return Every packet type. + */ + public static Iterable values() { + List> sources = Lists.newArrayList(); + sources.add(Handshake.Client.getInstance()); + sources.add(Handshake.Server.getInstance()); + sources.add(Play.Client.getInstance()); + sources.add(Play.Server.getInstance()); + sources.add(Status.Client.getInstance()); + sources.add(Status.Server.getInstance()); + sources.add(Login.Client.getInstance()); + sources.add(Login.Server.getInstance()); + return Iterables.concat(sources); + } + + /** + * Retrieve a packet type from a legacy (1.6.4 and below) packet ID. + * @param packetId - the legacy packet ID. + * @return The corresponding packet type. + * @throws IllegalArgumentException If the legacy packet could not be found. + */ + public static PacketType findLegacy(int packetId) { + PacketType type = getLookup().getFromLegacy(packetId); + + if (type != null) + return type; + throw new IllegalArgumentException("Cannot find legacy packet " + packetId); + } + + /** + * Retrieve a packet type from a protocol, sender and packet ID. + * @param protocol - the current protocol. + * @param sender - the sender. + * @param packetId - the packet ID. + * @return The corresponding packet type. + * @throws IllegalArgumentException If the current packet could not be found. + */ + public static PacketType findCurrent(Protocol protocol, Sender sender, int packetId) { + PacketType type = getLookup().getFromCurrent(protocol, sender, packetId); + + if (type != null) + return type; + throw new IllegalArgumentException("Cannot find packet " + packetId + + "(Protocol: " + protocol + ", Sender: " + sender + ")"); + } + + /** + * Retrieve a packet type from a protocol, sender and packet ID. + *

+ * The packet will automatically be registered if its missing. + * @param protocol - the current protocol. + * @param sender - the sender. + * @param packetId - the packet ID. + * @param legacyId - the legacy packet ID. Can be UNKNOWN_PACKET. + * @return The corresponding packet type. + */ + public static PacketType fromCurrent(Protocol protocol, Sender sender, int packetId, int legacyId) { + PacketType type = getLookup().getFromCurrent(protocol, sender, packetId); + + if (type == null) { + type = new PacketType(protocol, sender, packetId, legacyId); + + // Many may be scheduled, but only the first will be executed + scheduleRegister(type, "Dynamic-" + UUID.randomUUID().toString()); + } + return type; + } + + /** + * Register a particular packet type. + *

+ * Note that the registration will be performed on the main thread. + * @param type - the type to register. + * @param name - the name of the packet. + * @return A future telling us if our instance was registrered. + */ + public static Future scheduleRegister(final PacketType type, final String name) { + Callable callable = new Callable() { + @Override + public Boolean call() throws Exception { + ObjectEnum objEnum; + + // A bit ugly, but performance is critical + switch (type.getProtocol()) { + case HANDSHAKING: + objEnum = type.isClient() ? Handshake.Client.getInstance() : Handshake.Server.getInstance(); break; + case GAME: + objEnum = type.isClient() ? Play.Client.getInstance() : Play.Server.getInstance(); break; + case STATUS: + objEnum = type.isClient() ? Status.Client.getInstance() : Status.Server.getInstance(); break; + case LOGIN: + objEnum = type.isClient() ? Login.Client.getInstance() : Login.Server.getInstance(); break; + default: + throw new IllegalStateException("Unexpected protocol: " + type.getProtocol()); + } + + if (objEnum.registerMember(type, name)) { + getLookup().addPacketTypes(Arrays.asList(type)); + return true; + } + return false; + } + }; + + // Execute in the main thread if possible + if (Bukkit.getServer() == null || Bukkit.isPrimaryThread()) { + try { + return Futures.immediateFuture(callable.call()); + } catch (Exception e) { + return Futures.immediateFailedFuture(e); + } + } + return ProtocolLibrary.getExecutorSync().submit(callable); + } + + /** + * Construct a new packet type. + * @param protocol - the current protocol. + * @param target - the target - client or server. + * @param currentId - the current packet ID, or + * @param legacyId - the legacy packet ID. + */ + public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId) { + this(protocol, sender, currentId, legacyId, PROTOCOL_VERSION); + } + /** * Construct a new packet type. * @param protocol - the current protocol. * @param target - the target - client or server. * @param currentId - the current packet ID. * @param legacyId - the legacy packet ID. + * @param version - the version of the current ID. */ - public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId) { - this.protocol = protocol; - this.sender = sender; + public PacketType(Protocol protocol, Sender sender, int currentId, int legacyId, MinecraftVersion version) { + this.protocol = Preconditions.checkNotNull(protocol, "protocol cannot be NULL"); + this.sender = Preconditions.checkNotNull(sender, "sender cannot be NULL"); this.currentId = currentId; this.legacyId = legacyId; + this.version = version; + } + + /** + * Determine if this packet is supported on the current server. + * @return Whether or not the packet is supported. + */ + public boolean isSupported() { + return PacketRegistry.isSupported(this); } /** @@ -240,6 +571,22 @@ public class PacketType implements Serializable { return sender; } + /** + * Determine if this packet was sent by the client. + * @return TRUE if it was, FALSE otherwise. + */ + public boolean isClient() { + return sender == Sender.CLIENT; + } + + /** + * Determine if this packet was sent by the server. + * @return TRUE if it was, FALSE otherwise. + */ + public boolean isServer() { + return sender == Sender.SERVER; + } + /** * Retrieve the current protocol ID for this packet type. *

@@ -251,10 +598,26 @@ public class PacketType implements Serializable { } /** - * Retrieve the legacy (pre 1.7.2) protocol ID of the packet type. + * Retrieve the equivalent packet class. + * @return The packet class. + */ + public Class getPacketClass() { + return PacketRegistry.getPacketClassFromType(this); + } + + /** + * Retrieve the Minecraft version for the current ID. + * @return The Minecraft version. + */ + public MinecraftVersion getCurrentVersion() { + return version; + } + + /** + * Retrieve the legacy (1.6.4 or below) protocol ID of the packet type. *

* This ID is globally unique. - * @return The legacy ID. + * @return The legacy ID, or {@link #UNKNOWN_PACKET} if unknown. */ public int getLegacyId() { return legacyId; @@ -262,7 +625,7 @@ public class PacketType implements Serializable { @Override public int hashCode() { - return Objects.hashCode(protocol, sender, legacyId, currentId); + return Objects.hashCode(protocol, sender, currentId); } @Override @@ -281,6 +644,8 @@ public class PacketType implements Serializable { @Override public String toString() { - return "Packet [protocol=" + protocol + ", sender=" + sender + ", legacyId=" + legacyId + ", currentId=" + currentId + "]"; + Class clazz = getPacketClass(); + return (clazz != null ? clazz.getSimpleName() : "UNREGISTERED") + + " [" + protocol + ", " + sender + ", " + currentId + ", legacy: " + legacyId + "]"; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java new file mode 100644 index 00000000..5c2d6eb4 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketTypeLookup.java @@ -0,0 +1,90 @@ +package com.comphenix.protocol; + +import com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.PacketType.Sender; +import com.comphenix.protocol.collections.IntegerMap; +import com.google.common.base.Preconditions; + +/** + * Retrieve a packet type based on its version and ID, optionally with protocol and sender too. + * @author Kristian + */ +class PacketTypeLookup { + private static class ProtocolSenderLookup { + // Unroll lookup for performance reasons + public final IntegerMap HANDSHAKE_CLIENT = IntegerMap.newMap(); + public final IntegerMap HANDSHAKE_SERVER = IntegerMap.newMap(); + public final IntegerMap GAME_CLIENT = IntegerMap.newMap(); + public final IntegerMap GAME_SERVER = IntegerMap.newMap(); + public final IntegerMap STATUS_CLIENT = IntegerMap.newMap(); + public final IntegerMap STATUS_SERVER = IntegerMap.newMap(); + public final IntegerMap LOGIN_CLIENT = IntegerMap.newMap(); + public final IntegerMap LOGIN_SERVER = IntegerMap.newMap(); + + /** + * Retrieve the correct integer map for a specific protocol and sender. + * @param protocol - the protocol. + * @param sender - the sender. + * @return The integer map of packets. + */ + public IntegerMap getMap(Protocol protocol, Sender sender) { + switch (protocol) { + case HANDSHAKING: + return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER; + case GAME: + return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER; + case STATUS: + return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER; + case LOGIN: + return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER; + default: + throw new IllegalArgumentException("Unable to find protocol " + protocol); + } + } + } + + // Packet IDs from 1.6.4 and below + private final IntegerMap legacyLookup = new IntegerMap(); + + // Packets for 1.7.2 + private final ProtocolSenderLookup currentLookup = new ProtocolSenderLookup(); + + /** + * Add a collection of packet types to the lookup. + * @param types - the types to add. + */ + public PacketTypeLookup addPacketTypes(Iterable types) { + Preconditions.checkNotNull(types, "types cannot be NULL"); + + for (PacketType type : types) { + int legacy = type.getLegacyId(); + + // Skip unknown legacy packets + if (legacy != PacketType.UNKNOWN_PACKET) { + legacyLookup.put(type.getLegacyId(), type); + } + currentLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type); + } + return this; + } + + /** + * Retrieve a packet type from a legacy (1.6.4 and below) packet ID. + * @param packetId - the legacy packet ID. + * @return The corresponding packet type, or NULL if not found. + */ + public PacketType getFromLegacy(int packetId) { + return legacyLookup.get(packetId); + } + + /** + * Retrieve a packet type from a protocol, sender and packet ID. + * @param protocol - the current protocol. + * @param sender - the sender. + * @param packetId - the packet ID. + * @return The corresponding packet type, or NULL if not found. + */ + public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) { + return currentLookup.getMap(protocol, sender).get(packetId); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 48d242bf..18c62514 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -33,6 +33,7 @@ import org.bukkit.command.PluginCommand; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import com.comphenix.executors.BukkitExecutors; import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.error.BasicErrorReporter; import com.comphenix.protocol.error.DelegatedErrorReporter; @@ -54,6 +55,7 @@ import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; /** * The main entry point for ProtocolLib. @@ -113,12 +115,16 @@ public class ProtocolLibrary extends JavaPlugin { // Metrics and statistisc private Statistics statistisc; + + // Executors + private static ListeningScheduledExecutorService executorAsync; + private static ListeningScheduledExecutorService executorSync; // Structure compiler private BackgroundCompiler backgroundCompiler; - // Used to clean up server packets that have expired. - // But mostly required to simulate recieving client packets. + // Used to clean up server packets that have expired. But mostly required to simulate + // recieving client packets. private int asyncPacketTask = -1; private int tickCounter = 0; private static final int ASYNC_PACKET_DELAY = 1; @@ -141,7 +147,7 @@ public class ProtocolLibrary extends JavaPlugin { private CommandProtocol commandProtocol; private CommandPacket commandPacket; private CommandFilter commandFilter; - + // Whether or not disable is not needed private boolean skipDisable; @@ -150,6 +156,10 @@ public class ProtocolLibrary extends JavaPlugin { // Load configuration logger = getLoggerSafely(); + // Initialize executors + executorAsync = BukkitExecutors.newAsynchronous(this); + executorSync = BukkitExecutors.newSynchronous(this); + // Add global parameters DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this); reporter = getFilteredReporter(detailedReporter); @@ -526,6 +536,9 @@ public class ProtocolLibrary extends JavaPlugin { return; } + // Bukkit will shut down tasks on our executors + // ... + // Disable compiler if (backgroundCompiler != null) { backgroundCompiler.shutdownAll(); @@ -605,4 +618,24 @@ public class ProtocolLibrary extends JavaPlugin { public Statistics getStatistics() { return statistisc; } + + /** + * Retrieve an executor service for performing asynchronous tasks on the behalf of ProtocolLib. + *

+ * Note that this service is NULL if ProtocolLib has not been initialized yet. + * @return The executor service, or NULL. + */ + public static ListeningScheduledExecutorService getExecutorAsync() { + return executorAsync; + } + + /** + * Retrieve an executor service for performing synchronous tasks (main thread) on the behalf of ProtocolLib. + *

+ * Note that this service is NULL if ProtocolLib has not been initialized yet. + * @return The executor service, or NULL. + */ + public static ListeningScheduledExecutorService getExecutorSync() { + return executorSync; + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/collections/IntegerMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/collections/IntegerMap.java new file mode 100644 index 00000000..dde7331b --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/collections/IntegerMap.java @@ -0,0 +1,139 @@ +package com.comphenix.protocol.collections; + +import java.util.Arrays; +import java.util.Map; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +/** + * Represents a very quick integer-based lookup map, with a fixed key space size. + *

+ * Integers must be non-negative. + * @author Kristian + */ +public class IntegerMap { + private T[] array; + private int size; + + /** + * Construct a new integer map. + * @return A new integer map. + */ + public static IntegerMap newMap() { + return new IntegerMap(); + } + + /** + * Construct a new integer map with a default capacity. + */ + public IntegerMap() { + this(8); + } + + /** + * Construct a new integer map with a given capacity. + * @param initialCapacity - the capacity. + */ + public IntegerMap(int initialCapacity) { + @SuppressWarnings("unchecked") + T[] backingArray = (T[]) new Object[initialCapacity]; + this.array = backingArray; + this.size = 0; + } + + /** + * Associate an integer key with the given value. + * @param key - the integer key. Cannot be negative. + * @param value - the value. Cannot be NULL. + * @return The previous association, or NULL if not found. + */ + public T put(int key, T value) { + ensureCapacity(key); + + T old = array[key]; + array[key] = Preconditions.checkNotNull(value, "value cannot be NULL"); + + if (old == null) + size++; + return old; + } + + /** + * Remove an association from the map. + * @param key - the key of the association to remove. + * @return The old associated value, or NULL. + */ + public T remove(int key) { + T old = array[key]; + array[key] = null; + + if (old != null) + size--; + return old; + } + + /** + * Resize the backing array to fit the given key. + * @param key - the key. + */ + protected void ensureCapacity(int key) { + int newLength = array.length; + + // Don't resize if the key fits + if (key < 0) + throw new IllegalArgumentException("Negative key values are not permitted."); + if (key < newLength) + return; + + while (newLength <= key) { + int next = newLength * 2; + // Handle overflow + newLength = next > newLength ? next : Integer.MAX_VALUE; + } + this.array = Arrays.copyOf(array, newLength); + } + + /** + * Retrieve the number of mappings in this map. + * @return The number of mapping. + */ + public int size() { + return size; + } + + /** + * Retrieve the value associated with a given key. + * @param key - the key. + * @return The value, or NULL if not found. + */ + public T get(int key) { + if (key >= 0 && key < array.length) + return array[key]; + return null; + } + + /** + * Determine if the given key exists in the map. + * @param key - the key to check. + * @return TRUE if it does, FALSE otherwise. + */ + public boolean containsKey(int key) { + return get(key) != null; + } + + /** + * Convert the current map to an Integer map. + * @return The Integer map. + */ + public Map toMap() { + Map map = Maps.newHashMap(); + + for (int i = 0; i < array.length; i++) { + if (array[i] != null) { + map.put(i, array[i]); + } + } + return map; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java index e47ece45..ce0b401d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java @@ -31,6 +31,16 @@ public class NetworkMarker { // Cache serializer too private StreamSerializer serializer; + /** + * Construct a new network marker. + * @param side - whether or not this marker belongs to a client or server packet. + * @param inputBuffer - the read serialized packet data. + */ + public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer) { + this.side = Preconditions.checkNotNull(side, "side cannot be NULL."); + this.inputBuffer = Preconditions.checkNotNull(inputBuffer, "inputBuffer cannot be NULL."); + } + /** * Construct a new network marker. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 247d5150..4c665a1c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -28,21 +28,22 @@ import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import net.minecraft.util.io.netty.buffer.ByteBuf; +import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator; import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.entity.Entity; import org.bukkit.inventory.ItemStack; -import com.comphenix.protocol.Packets; -import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -58,6 +59,7 @@ import com.comphenix.protocol.reflect.cloning.SerializableCloner; import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.StreamSerializer; import com.comphenix.protocol.wrappers.BukkitConverters; @@ -69,6 +71,7 @@ import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; /** * Represents a Minecraft packet indirectly. @@ -76,13 +79,9 @@ import com.google.common.collect.Maps; * @author Kristian */ public class PacketContainer implements Serializable { - - /** - * Generated by Eclipse. - */ - private static final long serialVersionUID = 2074805748222377230L; + private static final long serialVersionUID = 3; - protected int id; + protected PacketType type; protected transient Object handle; // Current structure modifier @@ -118,15 +117,15 @@ public class PacketContainer implements Serializable { build(); // Packets that cannot be cloned by our default deep cloner - private static final IntegerSet CLONING_UNSUPPORTED = new IntegerSet(Packets.PACKET_COUNT, - Arrays.asList(Packets.Server.UPDATE_ATTRIBUTES)); + private static final Set CLONING_UNSUPPORTED = Sets.newHashSet( + PacketType.Play.Server.UPDATE_ATTRIBUTES, PacketType.Status.Server.KICK_DISCONNECT); /** * Creates a packet container for a new packet. * @param id - ID of the packet to create. */ public PacketContainer(int id) { - this(id, StructureCache.newPacket(id)); + this(PacketType.findLegacy(id), StructureCache.newPacket(PacketType.findLegacy(id))); } /** @@ -135,7 +134,7 @@ public class PacketContainer implements Serializable { * @param handle - contained packet. */ public PacketContainer(int id, Object handle) { - this(id, handle, StructureCache.getStructure(id).withTarget(handle)); + this(PacketType.findLegacy(id), handle); } /** @@ -145,10 +144,39 @@ public class PacketContainer implements Serializable { * @param structure - structure modifier. */ public PacketContainer(int id, Object handle, StructureModifier structure) { + this(PacketType.findLegacy(id), handle, structure); + } + + /** + * Creates a packet container for a new packet. + * @param type - the type of the packet to create. + */ + public PacketContainer(PacketType type) { + this(type, StructureCache.newPacket(type)); + } + + /** + * Creates a packet container for an existing packet. + * @param id - ID of the given packet. + * @param handle - contained packet. + */ + public PacketContainer(PacketType type, Object handle) { + this(type, handle, StructureCache.getStructure(type).withTarget(handle)); + } + + /** + * Creates a packet container for an existing packet. + * @param id - ID of the given packet. + * @param handle - contained packet. + * @param structure - structure modifier. + */ + public PacketContainer(PacketType type, Object handle, StructureModifier structure) { if (handle == null) throw new IllegalArgumentException("handle cannot be null."); + if (type == null) + throw new IllegalArgumentException("type cannot be null."); - this.id = id; + this.type = type; this.handle = handle; this.structureModifier = structure; } @@ -440,10 +468,21 @@ public class PacketContainer implements Serializable { /** * Retrieves the ID of this packet. + *

+ * Deprecated: Use {@link #getType()} instead. * @return Packet ID. */ + @Deprecated public int getID() { - return id; + return type.getLegacyId(); + } + + /** + * Retrieve the packet type of this packet. + * @return The packet type. + */ + public PacketType getType() { + return type; } /** @@ -473,12 +512,12 @@ public class PacketContainer implements Serializable { Object clonedPacket = null; // Fall back on the alternative (but slower) method of reading and writing back the packet - if (CLONING_UNSUPPORTED.contains(id)) { + if (CLONING_UNSUPPORTED.contains(type)) { clonedPacket = SerializableCloner.clone(this).getHandle(); } else { clonedPacket = DEEP_CLONER.clone(getHandle()); } - return new PacketContainer(getID(), clonedPacket); + return new PacketContainer(getType(), clonedPacket); } // To save space, we'll skip copying the inflated buffers in packet 51 and 56 @@ -511,10 +550,20 @@ public class PacketContainer implements Serializable { output.writeBoolean(handle != null); try { - // Call the write-method - getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class). - invoke(handle, new DataOutputStream(output)); - + if (MinecraftReflection.isUsingNetty()) { + ByteBuf buffer = createPacketBuffer(); + MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, buffer); + + output.writeInt(buffer.readableBytes()); + buffer.readBytes(output, buffer.readableBytes()); + + } else { + // Call the write-method + output.writeInt(-1); + getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class). + invoke(handle, new DataOutputStream(output)); + } + } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataOutputStream", e); } catch (IllegalAccessException e) { @@ -529,19 +578,28 @@ public class PacketContainer implements Serializable { input.defaultReadObject(); // Get structure modifier - structureModifier = StructureCache.getStructure(id); + structureModifier = StructureCache.getStructure(type); // Don't read NULL packets if (input.readBoolean()) { // Create a default instance of the packet - handle = StructureCache.newPacket(id); + handle = StructureCache.newPacket(type); // Call the read method try { - getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class). - invoke(handle, new DataInputStream(input)); - + if (MinecraftReflection.isUsingNetty()) { + ByteBuf buffer = createPacketBuffer(); + buffer.writeBytes(input, input.readInt()); + + MinecraftMethods.getPacketReadByteBufMethod().invoke(handle, buffer); + } else { + if (input.readInt() != -1) + throw new IllegalArgumentException("Cannot load a packet from 1.7.2 in 1.6.4."); + + getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class). + invoke(handle, new DataInputStream(input)); + } } catch (IllegalArgumentException e) { throw new IOException("Minecraft packet doesn't support DataInputStream", e); } catch (IllegalAccessException e) { @@ -555,6 +613,14 @@ public class PacketContainer implements Serializable { } } + /** + * Construct a new packet data serializer. + * @return The packet data serializer. + */ + private ByteBuf createPacketBuffer() { + return MinecraftReflection.getPacketDataSerializer(UnpooledByteBufAllocator.DEFAULT.buffer()); + } + /** * Retrieve the cached method concurrently. * @param lookup - a lazy lookup cache. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java index f6b52b55..86aa1035 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java @@ -25,6 +25,7 @@ import java.util.EventObject; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.async.AsyncMarker; import com.google.common.base.Preconditions; @@ -156,12 +157,23 @@ public class PacketEvent extends EventObject implements Cancellable { /** * Retrieves the packet ID. + *

+ * Deprecated: Use {@link #getPacketType()} instead. * @return The current packet ID. */ + @Deprecated public int getPacketID() { return packet.getID(); } + /** + * Retrieve the packet type. + * @return The type. + */ + public PacketType getPacketType() { + return packet.getType(); + } + /** * Retrieves whether or not the packet should be cancelled. * @return TRUE if it should be cancelled, FALSE otherwise. @@ -181,7 +193,7 @@ public class PacketEvent extends EventObject implements Cancellable { if (networkMarker == null) { if (isServerPacket()) { networkMarker = new NetworkMarker( - serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, null); + serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, (byte[]) null); } else { throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener."); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java index cc9ff538..772b6a29 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java @@ -45,6 +45,8 @@ import com.google.common.primitives.Primitives; * @author Kristian */ public class BukkitUnwrapper implements Unwrapper { + private static BukkitUnwrapper DEFAULT; + public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument."); public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation."); public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method."); @@ -56,6 +58,20 @@ public class BukkitUnwrapper implements Unwrapper { // The current error reporter private final ErrorReporter reporter; + /** + * Retrieve the default instance of the Bukkit unwrapper. + * @return The default instance. + */ + public static BukkitUnwrapper getInstance() { + ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter(); + + // Also recreate the unwrapper if the error reporter has changed + if (DEFAULT == null || DEFAULT.reporter != currentReporter) { + DEFAULT = new BukkitUnwrapper(currentReporter); + } + return DEFAULT; + } + /** * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter. */ diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java index f2e65038..4073d08a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java @@ -44,6 +44,7 @@ public interface ListenerInvoker { * @param packet - the packet. * @return The packet ID. */ + @Deprecated public abstract int getPacketID(Object packet); /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java index 5178abef..326cd03d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java @@ -2,6 +2,7 @@ package com.comphenix.protocol.injector; import org.bukkit.Bukkit; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.Packets; import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.events.ConnectionSide; @@ -17,6 +18,7 @@ class LoginPackets { private IntegerSet clientSide = new IntegerSet(Packets.PACKET_COUNT); private IntegerSet serverSide = new IntegerSet(Packets.PACKET_COUNT); + @SuppressWarnings("deprecation") public LoginPackets(MinecraftVersion version) { // Ordinary login clientSide.add(Packets.Client.HANDSHAKE); @@ -55,6 +57,7 @@ class LoginPackets { * @param side - the direction. * @return TRUE if it may, FALSE otherwise. */ + @Deprecated public boolean isLoginPacket(int packetId, ConnectionSide side) { switch (side) { case CLIENT_SIDE: @@ -68,4 +71,16 @@ class LoginPackets { throw new IllegalArgumentException("Unknown connection side: " + side); } } + + /** + * Determine if a given packet may be sent during login. + * @param type - the packet type. + * @return TRUE if it may, FALSE otherwise. + */ + public boolean isLoginPacket(PacketType type) { + return PacketType.Login.Client.getInstance().hasMember(type) || + PacketType.Login.Server.getInstance().hasMember(type) || + PacketType.Status.Client.getInstance().hasMember(type) || + PacketType.Status.Server.getInstance().hasMember(type); + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index 57105ffc..70c5e026 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -21,6 +21,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.error.RethrowErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.packet.PacketRegistry; @@ -36,7 +37,6 @@ import com.google.common.primitives.Primitives; * */ public class PacketConstructor { - /** * A packet constructor that automatically converts Bukkit types to their NMS conterpart. *

@@ -48,7 +48,7 @@ public class PacketConstructor { private Constructor constructorMethod; // The packet ID - private int packetID; + private PacketType type; // Used to unwrap Bukkit objects private List unwrappers; @@ -62,8 +62,8 @@ public class PacketConstructor { this.unwrappers.addAll(BukkitConverters.getUnwrappers()); } - private PacketConstructor(int packetID, Constructor constructorMethod, List unwrappers, Unwrapper[] paramUnwrapper) { - this.packetID = packetID; + private PacketConstructor(PacketType type, Constructor constructorMethod, List unwrappers, Unwrapper[] paramUnwrapper) { + this.type = type; this.constructorMethod = constructorMethod; this.unwrappers = unwrappers; this.paramUnwrapper = paramUnwrapper; @@ -75,10 +75,21 @@ public class PacketConstructor { /** * Retrieve the id of the packets this constructor creates. + *

+ * Deprecated: Use {@link #getType()} instead. * @return The ID of the packets this constructor will create. */ + @Deprecated public int getPacketID() { - return packetID; + return type.getLegacyId(); + } + + /** + * Retrieve the type of the packets this constructor creates. + * @return The type of the created packets. + */ + public PacketType getType() { + return type; } /** @@ -87,19 +98,35 @@ public class PacketConstructor { * @return A constructor with a different set of unwrappers. */ public PacketConstructor withUnwrappers(List unwrappers) { - return new PacketConstructor(packetID, constructorMethod, unwrappers, paramUnwrapper); + return new PacketConstructor(type, constructorMethod, unwrappers, paramUnwrapper); } /** - * Create a packet constructor that creates packets using the given types. + * Create a packet constructor that creates packets using the given ID. *

* Note that if you pass a Class as a value, it will use its type directly. - * @param id - packet ID. + *

+ * Deprecated: Use {@link #withPacket(PacketType, Object[])} instead. + * @param id - legacy (1.6.4) packet ID. * @param values - the values that will match each parameter in the desired constructor. * @return A packet constructor with these types. * @throws IllegalArgumentException If no packet constructor could be created with these types. */ + @Deprecated public PacketConstructor withPacket(int id, Object[] values) { + return withPacket(PacketType.findLegacy(id), values); + } + + /** + * Create a packet constructor that creates packets using the given types. + *

+ * Note that if you pass a Class as a value, it will use its type directly. + * @param type - the type of the packet to create. + * @param values - the values that will match each parameter in the desired constructor. + * @return A packet constructor with these types. + * @throws IllegalArgumentException If no packet constructor could be created with these types. + */ + public PacketConstructor withPacket(PacketType type, Object[] values) { Class[] types = new Class[values.length]; Throwable lastException = null; Unwrapper[] paramUnwrapper = new Unwrapper[values.length]; @@ -131,11 +158,10 @@ public class PacketConstructor { types[i] = Object.class; } } - - Class packetType = PacketRegistry.getPacketClassFromID(id, true); + Class packetType = PacketRegistry.getPacketClassFromType(type, true); if (packetType == null) - throw new IllegalArgumentException("Could not find a packet by the id " + id); + throw new IllegalArgumentException("Could not find a packet by the type " + type); // Find the correct constructor for (Constructor constructor : packetType.getConstructors()) { @@ -143,10 +169,9 @@ public class PacketConstructor { if (isCompatible(types, params)) { // Right, we've found our type - return new PacketConstructor(id, constructor, unwrappers, paramUnwrapper); + return new PacketConstructor(type, constructor, unwrappers, paramUnwrapper); } } - throw new IllegalArgumentException("No suitable constructor could be found.", lastException); } @@ -168,7 +193,7 @@ public class PacketConstructor { } Object nmsPacket = constructorMethod.newInstance(values); - return new PacketContainer(packetID, nmsPacket); + return new PacketContainer(type, nmsPacket); } catch (IllegalArgumentException e) { throw e; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 140b82ce..dbdde496 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -57,6 +57,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.*; +import com.comphenix.protocol.injector.netty.NettyProtocolInjector; import com.comphenix.protocol.injector.packet.InterceptWritePacket; import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; @@ -181,6 +182,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Spigot listener, if in use private SpigotPacketInjector spigotInjector; + // Netty injector (for 1.7.2) + private NettyProtocolInjector nettyInjector; + // Plugin verifier private PluginVerifier pluginVerifier; @@ -231,7 +235,12 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok this.interceptWritePacket = new InterceptWritePacket(classLoader, reporter); // Use the correct injection type - if (builder.isNettyEnabled()) { + if (MinecraftReflection.isUsingNetty()) { + this.nettyInjector = new NettyProtocolInjector(this); + this.playerInjection = nettyInjector.getPlayerInjector(); + this.packetInjector = nettyInjector.getPacketInjector(); + + } else if (builder.isNettyEnabled()) { this.spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); this.playerInjection = spigotInjector.getPlayerHandler(); this.packetInjector = spigotInjector.getPacketInjector(); @@ -780,7 +789,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (filters) { byte[] data = NetworkMarker.getByteBuffer(marker); - PacketEvent event = packetInjector.packetRecieved(packet, sender, data); + PacketEvent event = packetInjector.packetRecieved(packet, sender, data); if (!event.isCancelled()) mcPacket = event.getPacket().getHandle(); @@ -881,6 +890,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok public void registerEvents(PluginManager manager, final Plugin plugin) { if (spigotInjector != null && !spigotInjector.register(plugin)) throw new IllegalArgumentException("Spigot has already been registered."); + if (nettyInjector != null) + nettyInjector.inject(); try { manager.registerEvents(new Listener() { @@ -1009,7 +1020,6 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Yes, this is crazy. @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerOld(PluginManager manager, final Plugin plugin) { - try { ClassLoader loader = manager.getClass().getClassLoader(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java index d5455e9f..46d45564 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/StructureCache.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; @@ -35,19 +36,31 @@ import com.comphenix.protocol.utility.MinecraftReflection; */ public class StructureCache { // Structure modifiers - private static ConcurrentMap> structureModifiers = - new ConcurrentHashMap>(); + private static ConcurrentMap> structureModifiers = + new ConcurrentHashMap>(); - private static Set compiling = new HashSet(); + private static Set compiling = new HashSet(); /** - * Creates an empty Minecraft packet of the given ID. - * @param id - packet ID. + * Creates an empty Minecraft packet of the given id. + *

+ * Decreated: Use {@link #newPacket(PacketType)} instead. + * @param legacyId - legacy (1.6.4) packet id. * @return Created packet. */ - public static Object newPacket(int id) { + @Deprecated + public static Object newPacket(int legacyId) { + return newPacket(PacketType.findLegacy(legacyId)); + } + + /** + * Creates an empty Minecraft packet of the given type. + * @param type - packet type. + * @return Created packet. + */ + public static Object newPacket(PacketType type) { try { - return PacketRegistry.getPacketClassFromID(id, true).newInstance(); + return PacketRegistry.getPacketClassFromType(type, true).newInstance(); } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { @@ -57,12 +70,24 @@ public class StructureCache { /** * Retrieve a cached structure modifier for the given packet id. - * @param id - packet ID. + *

+ * Deprecated: Use {@link #getStructure(PacketType)} instead. + * @param legacyId - the legacy (1.6.4) packet ID. * @return A structure modifier. */ - public static StructureModifier getStructure(int id) { + @Deprecated + public static StructureModifier getStructure(int legacyId) { + return getStructure(PacketType.findLegacy(legacyId)); + } + + /** + * Retrieve a cached structure modifier for the given packet type. + * @param type - packet type. + * @return A structure modifier. + */ + public static StructureModifier getStructure(PacketType type) { // Compile structures by default - return getStructure(id, true); + return getStructure(type, true); } /** @@ -83,26 +108,38 @@ public class StructureCache { */ public static StructureModifier getStructure(Class packetType, boolean compile) { // Get the ID from the class - return getStructure(PacketRegistry.getPacketID(packetType), compile); + return getStructure(PacketRegistry.getPacketType(packetType), compile); } /** - * Retrieve a cached structure modifier for the given packet id. - * @param id - packet ID. + * Retrieve a cached structure modifier for the given packet ID. + *

+ * Deprecated: Use {@link #getStructure(PacketType, boolean)} instead. + * @param legacyId - the legacy (1.6.4) packet ID. * @param compile - whether or not to asynchronously compile the structure modifier. * @return A structure modifier. */ - public static StructureModifier getStructure(int id, boolean compile) { - - StructureModifier result = structureModifiers.get(id); + @Deprecated + public static StructureModifier getStructure(final int legacyId, boolean compile) { + return getStructure(PacketType.findLegacy(legacyId), compile); + } + + /** + * Retrieve a cached structure modifier for the given packet type. + * @param type - packet type. + * @param compile - whether or not to asynchronously compile the structure modifier. + * @return A structure modifier. + */ + public static StructureModifier getStructure(final PacketType type, boolean compile) { + StructureModifier result = structureModifiers.get(type); // We don't want to create this for every lookup if (result == null) { // Use the vanilla class definition final StructureModifier value = new StructureModifier( - PacketRegistry.getPacketClassFromID(id, true), MinecraftReflection.getPacketClass(), true); + PacketRegistry.getPacketClassFromType(type, true), MinecraftReflection.getPacketClass(), true); - result = structureModifiers.putIfAbsent(id, value); + result = structureModifiers.putIfAbsent(type, value); // We may end up creating multiple modifiers, but we'll agree on which to use if (result == null) { @@ -114,21 +151,19 @@ public class StructureCache { if (compile && !(result instanceof CompiledStructureModifier)) { // Compilation is many orders of magnitude slower than synchronization synchronized (compiling) { - final int idCopy = id; final BackgroundCompiler compiler = BackgroundCompiler.getInstance(); - if (!compiling.contains(id) && compiler != null) { + if (!compiling.contains(type) && compiler != null) { compiler.scheduleCompilation(result, new CompileListener() { @Override public void onCompiled(StructureModifier compiledModifier) { - structureModifiers.put(idCopy, compiledModifier); + structureModifiers.put(type, compiledModifier); } }); - compiling.add(id); + compiling.add(type); } } } - return result; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java new file mode 100644 index 00000000..74251f0b --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/BootstrapList.java @@ -0,0 +1,92 @@ +package com.comphenix.protocol.injector.netty; + +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.ForwardingList; +import com.google.common.collect.Lists; + +// Hopefully, CB won't version these as well +import net.minecraft.util.io.netty.channel.ChannelFuture; +import net.minecraft.util.io.netty.channel.ChannelHandler; + +class BootstrapList extends ForwardingList { + private List delegate; + private ChannelHandler handler; + + /** + * Construct a new bootstrap list. + * @param delegate - the delegate. + * @param handler - the channel handler to add. + */ + public BootstrapList(List delegate, ChannelHandler handler) { + this.delegate = delegate; + this.handler = handler; + + // Process all existing bootstraps + for (ChannelFuture future : this) + processBootstrap(future); + } + + @Override + protected List delegate() { + return delegate; + } + + @Override + public boolean add(ChannelFuture element) { + processBootstrap(element); + return super.add(element); + } + + @Override + public boolean addAll(Collection collection) { + List copy = Lists.newArrayList(collection); + + // Process the collection before we pass it on + for (ChannelFuture future : copy) { + processBootstrap(future); + } + return super.addAll(copy); + } + + @Override + public ChannelFuture set(int index, ChannelFuture element) { + ChannelFuture old = super.set(index, element); + + // Handle the old future, and the newly inserted future + if (old != element) { + if (old != null) { + unprocessBootstrap(old); + } + if (element != null) { + processBootstrap(element); + } + } + return old; + } + + /** + * Process a single channel future. + * @param future - the future. + */ + protected void processBootstrap(ChannelFuture future) { + future.channel().pipeline().addLast(handler); + } + + /** + * Revert any changes we made to the channel future. + * @param future - the future. + */ + protected void unprocessBootstrap(ChannelFuture future) { + future.channel().pipeline().remove(handler); + } + + /** + * Close and revert all changes. + */ + public void close() { + for (ChannelFuture future : this) + unprocessBootstrap(future); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java new file mode 100644 index 00000000..12471568 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java @@ -0,0 +1,538 @@ +package com.comphenix.protocol.injector.netty; + +import java.lang.reflect.InvocationTargetException; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; + +import net.minecraft.util.io.netty.buffer.ByteBuf; +import net.minecraft.util.io.netty.channel.Channel; +import net.minecraft.util.io.netty.channel.ChannelHandler; +import net.minecraft.util.io.netty.channel.ChannelHandlerContext; +import net.minecraft.util.io.netty.channel.socket.SocketChannel; +import net.minecraft.util.io.netty.handler.codec.ByteToMessageDecoder; +import net.minecraft.util.io.netty.handler.codec.MessageToByteEncoder; +import net.minecraft.util.io.netty.util.concurrent.GenericFutureListener; +import net.sf.cglib.proxy.Factory; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketOutputHandler; +import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.injector.server.SocketInjector; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.VolatileField; +import com.comphenix.protocol.reflect.FuzzyReflection.FieldAccessor; +import com.comphenix.protocol.reflect.FuzzyReflection.MethodAccessor; +import com.comphenix.protocol.utility.MinecraftFields; +import com.comphenix.protocol.utility.MinecraftMethods; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.base.Preconditions; +import com.google.common.collect.MapMaker; + +/** + * Represents a channel injector. + * @author Kristian + */ +class ChannelInjector extends ByteToMessageDecoder { + /** + * Represents a listener for received or sent packets. + * @author Kristian + */ + interface ChannelListener { + /** + * Invoked when a packet is being sent to the client. + *

+ * This is invoked on the main thread. + * @param injector - the channel injector. + * @param packet - the packet. + * @param marker - the associated network marker, if any. + * @return The new packet, if it should be changed, or NULL to cancel. + */ + public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker); + + /** + * Invoked when a packet is being received from a client. + *

+ * This is invoked on an asynchronous worker thread. + * @param injector - the channel injector. + * @param packet - the packet. + * @param marker - the associated network marker, if any. + * @return The new packet, if it should be changed, or NULL to cancel. + */ + public Object onPacketReceiving(ChannelInjector injector, Object packet, NetworkMarker marker); + + /** + * Determine if we need the buffer data of a given client side packet. + * @param packetId - the packet Id. + * @return TRUE if we do, FALSE otherwise. + */ + public boolean includeBuffer(int packetId); + } + + private static final ConcurrentMap cachedInjector = new MapMaker().weakKeys().makeMap(); + + // The player, or temporary player + private Player player; + + // The player connection + private Object playerConnection; + + // For retrieving the protocol + private FieldAccessor protocolAccessor; + + // The current network manager and channel + private final Object networkManager; + private final Channel originalChannel; + private VolatileField channelField; + + // Known network markers + private ConcurrentMap packetMarker = new MapMaker().weakKeys().makeMap(); + private ConcurrentMap markerEvent = new MapMaker().weakKeys().makeMap(); + + // Packets to ignore + private Set ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); + + // Other handlers + private ByteToMessageDecoder vanillaDecoder; + private MessageToByteEncoder vanillaEncoder; + private MethodAccessor decodeBuffer; + private MethodAccessor encodeBuffer; + + // Our extra handler + private MessageToByteEncoder protocolEncoder; + + // The channel listener + private ChannelListener channelListener; + + // Closed + private boolean injected; + private boolean closed; + + /** + * Construct a new channel injector. + * @param player - the current player, or temporary player. + * @param networkManager - its network manager. + * @param channel - its channel. + */ + private ChannelInjector(Player player, Object networkManager, Channel channel, ChannelListener channelListener) { + this.player = Preconditions.checkNotNull(player, "player cannot be NULL"); + this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL"); + this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL"); + this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL"); + + // Get the channel field + this.channelField = new VolatileField( + FuzzyReflection.fromObject(networkManager, true). + getFieldByType("channel", Channel.class), networkManager); + } + + /** + * Construct or retrieve a channel injector from an existing Bukkit player. + * @param player - the existing Bukkit player. + * @return A new injector, or an existing injector associated with this player. + */ + public static ChannelInjector fromPlayer(Player player, ChannelListener listener) { + ChannelInjector injector = cachedInjector.get(player); + + if (injector != null) + return injector; + + Object networkManager = MinecraftFields.getNetworkManager(player); + Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true); + + // See if a channel has already been created + injector = (ChannelInjector) findChannelHandler(channel, ChannelInjector.class); + + if (injector != null) { + // Update the player instance + injector.player = player; + } else { + injector = new ChannelInjector(player, networkManager, channel, listener); + } + // Cache injector and return + cachedInjector.put(player, injector); + return injector; + } + + /** + * Construct a new channel injector for the given channel. + * @param channel - the channel. + * @param playerFactory - a temporary player creator. + * @return The channel injector. + */ + public static ChannelInjector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) { + Object networkManager = findNetworkManager(channel); + Player temporaryPlayer = playerFactory.createTemporaryPlayer(Bukkit.getServer()); + ChannelInjector injector = new ChannelInjector(temporaryPlayer, networkManager, channel, listener); + + // Initialize temporary player + TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, new ChannelSocketInjector(injector)); + return injector; + } + + /** + * Inject the current channel. + */ + @SuppressWarnings("unchecked") + public boolean inject() { + synchronized (networkManager) { + if (originalChannel instanceof Factory) + return false; + + // Don't inject the same channel twice + if (findChannelHandler(originalChannel, ChannelInjector.class) != null) { + // Invalidate cache + if (player != null) + cachedInjector.remove(player); + return false; + } + + // Get the vanilla decoder, so we don't have to replicate the work + vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder"); + vanillaEncoder = (MessageToByteEncoder) originalChannel.pipeline().get("encoder"); + decodeBuffer = FuzzyReflection.getMethodAccessor(vanillaDecoder.getClass(), + "decode", ChannelHandlerContext.class, ByteBuf.class, List.class); + encodeBuffer = FuzzyReflection.getMethodAccessor(vanillaEncoder.getClass(), + "encode", ChannelHandlerContext.class, Object.class, ByteBuf.class); + + protocolEncoder = new MessageToByteEncoder() { + @Override + protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { + NetworkMarker marker = getMarker(output); + PacketEvent event = markerEvent.remove(marker); + + if (event != null && NetworkMarker.hasOutputHandlers(marker)) { + ByteBuf packetBuffer = ctx.alloc().buffer(); + encodeBuffer.invoke(vanillaEncoder, ctx, packet, packetBuffer); + byte[] data = getBytes(packetBuffer); + + for (PacketOutputHandler handler : marker.getOutputHandlers()) { + handler.handle(event, data); + } + // Write the result + output.writeBytes(data); + } + } + }; + + // Insert our handler - note that we replace the decoder with our own + originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this); + originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder); + + // Intercept all write methods + channelField.setValue(new ChannelProxy() { + @Override + protected Object onMessageWritten(Object message) { + return channelListener.onPacketSending(ChannelInjector.this, message, packetMarker.get(message)); + } + }.asChannel(originalChannel)); + + injected = true; + return true; + } + } + + /** + * Close the current injector. + */ + public void close() { + if (!closed) { + closed = true; + + if (injected) { + originalChannel.pipeline().remove(this); + originalChannel.pipeline().remove(protocolEncoder); + channelField.revertValue(); + } + } + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List packets) throws Exception { + byteBuffer.markReaderIndex(); + decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets); + + if (packets.size() > 0) { + Object input = packets.get(0); + int id = PacketRegistry.getPacketID(input.getClass()); + NetworkMarker marker = null; + + if (channelListener.includeBuffer(id)) { + byteBuffer.resetReaderIndex(); + marker = new NetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer)); + } + Object output = channelListener.onPacketReceiving(this, input, marker); + + // Handle packet changes + if (output == null) + packets.clear(); + else if (output != input) + packets.set(0, output); + } + } + + /** + * Retrieve every byte in the given byte buffer. + * @param buffer - the buffer. + * @return The bytes. + */ + private byte[] getBytes(ByteBuf buffer){ + byte[] data = new byte[buffer.readableBytes()]; + + buffer.readBytes(data); + return data; + } + + /** + * Disconnect the current player. + * @param message - the disconnect message, if possible. + */ + private void disconnect(String message) { + // If we're logging in, we can only close the channel + if (playerConnection == null || player instanceof Factory) { + originalChannel.disconnect(); + } else { + // Call the disconnect method + try { + MinecraftMethods.getDisconnectMethod(playerConnection.getClass()). + invoke(playerConnection, message); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to invoke disconnect method.", e); + } + } + } + + /** + * Send a packet to a player's client. + * @param packet - the packet to send. + * @param marker - the network marker. + * @param filtered - whether or not the packet is filtered. + */ + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { + saveMarker(packet, marker); + + // Record if this packet should be ignored by most listeners + if (!filtered) { + ignoredPackets.add(packet); + } + + // Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active + try { + if (player instanceof Factory) { + MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet, new GenericFutureListener[0]); + } else { + MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet); + } + } catch (Exception e) { + throw new RuntimeException("Unable to send server packet " + packet, e); + } + } + + /** + * Recieve a packet on the server. + * @param packet - the (NMS) packet to send. + * @param marker - the network marker. + * @param filtered - whether or not the packet is filtered. + */ + public void recieveClientPacket(Object packet, NetworkMarker marker, boolean filtered) { + saveMarker(packet, marker); + + if (!filtered) { + ignoredPackets.add(packet); + } + + try { + MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(networkManager, null, packet); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to recieve client packet " + packet, e); + } + } + + /** + * Retrieve the current protocol state. + * @return The current protocol. + */ + public Protocol getCurrentProtocol() { + if (protocolAccessor == null) { + protocolAccessor = FuzzyReflection.getFieldAccessor( + networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true); + } + return Protocol.fromVanilla((Enum) protocolAccessor.get(networkManager)); + } + + /** + * Retrieve the player connection of the current player. + * @return The player connection. + */ + private Object getPlayerConnection() { + if (playerConnection == null) { + playerConnection = MinecraftFields.getPlayerConnection(player); + } + return playerConnection; + } + + /** + * Undo the ignore status of a packet. + * @param packet - the packet. + * @return TRUE if the ignore status was undone, FALSE otherwise. + */ + public boolean unignorePacket(Object packet) { + return ignoredPackets.remove(packet); + } + + /** + * Ignore the given packet. + * @param packet - the packet to ignore. + * @return TRUE if it was ignored, FALSE if it already is ignored. + */ + public boolean ignorePacket(Object packet) { + return ignoredPackets.add(packet); + } + + /** + * Retrieve the network marker associated with a given packet. + * @param packet - the packet. + * @return The network marker. + */ + public NetworkMarker getMarker(Object packet) { + return packetMarker.get(packet); + } + + /** + * Associate a given network marker with a specific packet. + * @param packet - the NMS packet. + * @param marker - the associated marker. + */ + public void saveMarker(Object packet, NetworkMarker marker) { + if (marker != null) { + packetMarker.put(packet, marker); + } + } + + /** + * Associate a given network marker with a packet event. + * @param marker - the marker. + * @param event - the packet event + */ + public void saveEvent(NetworkMarker marker, PacketEvent event) { + if (marker != null) { + markerEvent.put(marker, event); + } + } + + /** + * Find the network manager in a channel's pipeline. + * @param channel - the channel. + * @return The network manager. + */ + private static Object findNetworkManager(Channel channel) { + // Find the network manager + Object networkManager = findChannelHandler(channel, MinecraftReflection.getNetworkManagerClass()); + + if (networkManager != null) + return networkManager; + throw new IllegalArgumentException("Unable to find NetworkManager in " + channel); + } + + /** + * Find the first channel handler that is assignable to a given type. + * @param channel - the channel. + * @param clazz - the type. + * @return The first handler, or NULL. + */ + private static ChannelHandler findChannelHandler(Channel channel, Class clazz) { + for (Entry entry : channel.pipeline()) { + if (clazz.isAssignableFrom(entry.getValue().getClass())) { + return entry.getValue(); + } + } + return null; + } + + /** + * Retrieve the current player or temporary player associated with the injector. + * @return The current player. + */ + public Player getPlayer() { + return player; + } + + /** + * Determine if the channel has already been injected. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean isInjected() { + return injected; + } + + /** + * Determine if this channel has been closed and cleaned up. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean isClosed() { + return closed; + } + + /** + * Represents a socket injector that foreards to the current channel injector. + * @author Kristian + */ + private static class ChannelSocketInjector implements SocketInjector { + private final ChannelInjector injector; + + public ChannelSocketInjector(ChannelInjector injector) { + this.injector = injector; + } + + @Override + public Socket getSocket() throws IllegalAccessException { + return NettySocketAdaptor.adapt((SocketChannel) injector.originalChannel); + } + + @Override + public SocketAddress getAddress() throws IllegalAccessException { + return injector.originalChannel.localAddress(); + } + + @Override + public void disconnect(String message) throws InvocationTargetException { + injector.disconnect(message); + } + + @Override + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException { + injector.sendServerPacket(packet, marker, filtered); + } + + @Override + public Player getPlayer() { + return injector.player; + } + + @Override + public Player getUpdatedPlayer() { + return injector.player; + } + + @Override + public void transferState(SocketInjector delegate) { + // Do nothing + } + + @Override + public void setUpdatedPlayer(Player updatedPlayer) { + injector.player = updatedPlayer; + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java new file mode 100644 index 00000000..9d72dc17 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelProxy.java @@ -0,0 +1,57 @@ +package com.comphenix.protocol.injector.netty; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; + +import net.minecraft.util.com.google.common.collect.Sets; +import net.minecraft.util.io.netty.channel.Channel; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +abstract class ChannelProxy { + private static Set WRITE_METHODS; + + /** + * Retrieve the channel proxy object. + * @param proxyInstance - the proxy instance object. + * @return The channel proxy. + */ + public Channel asChannel(final Channel proxyInstance) { + // Simple way to match all the write methods + if (WRITE_METHODS == null) { + List writers = FuzzyReflection.fromClass(Channel.class). + getMethodList(FuzzyMethodContract.newBuilder().nameRegex("write.*").build()); + WRITE_METHODS = Sets.newHashSet(writers); + } + + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(Channel.class); + enhancer.setCallback(new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + if (WRITE_METHODS.contains(method)) { + args[0] = onMessageWritten(args[0]); + + // If we should skip this object + if (args[0] == null) + return null; + } + // Forward to proxy + return proxy.invoke(proxyInstance, args); + } + }); + return (Channel) enhancer.create(); + } + + /** + * Invoked when a packet is being transmitted. + * @param message - the packet to transmit. + * @return The object to transmit. + */ + protected abstract Object onMessageWritten(Object message); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocol.java deleted file mode 100644 index 87f309e9..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocol.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.comphenix.protocol.injector.netty; - -import com.comphenix.protocol.utility.MinecraftReflection; - -/** - * Represents a way of accessing the new netty Protocol enum. - * @author Kristian - */ -public class NettyProtocol { - private Class enumProtocol; - - - - public NettyProtocol() { - enumProtocol = MinecraftReflection.getEnumProtocolClass(); - - - } - - /** - * Load the packet lookup tables in each protocol. - */ - private void initialize() { - for (Object protocol : enumProtocol.getEnumConstants()) { - - } - } -} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java new file mode 100644 index 00000000..6b062049 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java @@ -0,0 +1,309 @@ +package com.comphenix.protocol.injector.netty; + +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Set; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import net.minecraft.util.io.netty.channel.Channel; +import net.minecraft.util.io.netty.channel.ChannelFuture; +import net.minecraft.util.io.netty.channel.ChannelHandler; +import net.minecraft.util.io.netty.channel.ChannelHandlerContext; +import net.minecraft.util.io.netty.channel.ChannelInboundHandler; +import net.minecraft.util.io.netty.channel.ChannelInboundHandlerAdapter; +import net.minecraft.util.io.netty.channel.ChannelInitializer; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.ListenerInvoker; +import com.comphenix.protocol.injector.netty.ChannelInjector.ChannelListener; +import com.comphenix.protocol.injector.packet.PacketInjector; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; +import com.comphenix.protocol.injector.spigot.AbstractPacketInjector; +import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.VolatileField; +import com.comphenix.protocol.utility.MinecraftReflection; + +public class NettyProtocolInjector implements ChannelListener { + private volatile boolean injected; + private volatile boolean closed; + + // The temporary player factory + private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); + private VolatileField bootstrapField; + + // Different sending filters + private IntegerSet queuedFilters = new IntegerSet(Packets.PACKET_COUNT); + private IntegerSet reveivedFilters = new IntegerSet(Packets.PACKET_COUNT); + + // Which packets are buffered + private Set bufferedPackets; + + private ListenerInvoker invoker; + + public NettyProtocolInjector(ListenerInvoker invoker) { + this.invoker = invoker; + } + + /** + * Inject into the spigot connection class. + */ + @SuppressWarnings("unchecked") + public synchronized void inject() { + if (injected) + throw new IllegalStateException("Cannot inject twice."); + try { + FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass()); + Method serverConnectionMethod = fuzzyServer.getMethodByParameters("getServerConnection", MinecraftReflection.getServerConnectionClass(), new Class[] {}); + + // Get the server connection + Object server = fuzzyServer.getSingleton(); + Object serverConnection = serverConnectionMethod.invoke(server); + + // Handle connected channels + final ChannelInboundHandler initProtocol = new ChannelInitializer() { + @Override + protected void initChannel(Channel channel) throws Exception { + // Check and see if the injector has closed + synchronized (this) { + if (closed) + return; + } + ChannelInjector.fromChannel(channel, NettyProtocolInjector.this, playerFactory).inject(); + } + }; + + // Add our handler to newly created channels + final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Channel channel = (Channel) msg; + + // Execute the other handlers before adding our own + ctx.fireChannelRead(msg); + channel.pipeline().addLast(initProtocol); + } + }; + + // Insert ProtocolLib's connection interceptor + bootstrapField = getBootstrapField(serverConnection); + bootstrapField.setValue(new BootstrapList( + (List) bootstrapField.getValue(), connectionHandler + )); + + injected = true; + + } catch (Exception e) { + throw new RuntimeException("Unable to inject channel futures.", e); + } + } + + /** + * Inject our packet handling into a specific player. + * @param player + */ + public void injectPlayer(Player player) { + ChannelInjector.fromPlayer(player, this).inject(); + } + + private VolatileField getBootstrapField(Object serverConnection) { + VolatileField firstVolatile = null; + + for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) { + VolatileField currentVolatile = new VolatileField(field, serverConnection, true); + @SuppressWarnings("unchecked") + List list = (List) currentVolatile.getValue(); + + // Also save the first list + if (firstVolatile == null) { + firstVolatile = currentVolatile; + } + if (list.size() > 0 && list.get(0) instanceof ChannelFuture) { + return currentVolatile; + } + } + return firstVolatile; + } + + /** + * Clean up any remaning injections. + */ + public synchronized void close() { + if (!closed) { + closed = true; + + @SuppressWarnings("unchecked") + List bootstraps = (List) bootstrapField.getValue(); + + // Remember to close all the bootstraps + for (Object value : bootstraps) { + if (value instanceof BootstrapList) { + ((BootstrapList) value).close(); + } + } + + // Uninject all the players + for (Player player : Bukkit.getServer().getOnlinePlayers()) { + ChannelInjector.fromPlayer(player, this).close(); + } + bootstrapField.revertValue(); + } + } + + @Override + public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker) { + Integer id = invoker.getPacketID(packet); + + if (id != null && queuedFilters.contains(id)) { + // Check for ignored packets + if (injector.unignorePacket(packet)) { + return packet; + } + PacketContainer container = new PacketContainer(id, packet); + PacketEvent event = packetQueued(container, injector.getPlayer()); + + if (!event.isCancelled()) { + injector.saveEvent(marker, event); + return event.getPacket().getHandle(); + } else { + return null; // Cancel + } + } + // Don't change anything + return packet; + } + + @Override + public Object onPacketReceiving(ChannelInjector injector, Object packet, NetworkMarker marker) { + Integer id = invoker.getPacketID(packet); + + if (id != null && reveivedFilters.contains(id)) { + // Check for ignored packets + if (injector.unignorePacket(packet)) { + return packet; + } + PacketContainer container = new PacketContainer(id, packet); + PacketEvent event = packetReceived(container, injector.getPlayer(), marker); + + if (!event.isCancelled()) { + return event.getPacket().getHandle(); + } else { + return null; // Cancel + } + } + // Don't change anything + return packet; + } + + @Override + public boolean includeBuffer(int packetId) { + return bufferedPackets.contains(packetId); + } + + /** + * Called to inform the event listeners of a queued packet. + * @param packet - the packet that is to be sent. + * @param reciever - the reciever of this packet. + * @return The packet event that was used. + */ + private PacketEvent packetQueued(PacketContainer packet, Player reciever) { + PacketEvent event = PacketEvent.fromServer(this, packet, reciever); + + invoker.invokePacketSending(event); + return event; + } + + /** + * Called to inform the event listeners of a received packet. + * @param packet - the packet that has been receieved. + * @param sender - the client packet. + * @param marker - the network marker. + * @return The packet event that was used. + */ + private PacketEvent packetReceived(PacketContainer packet, Player sender, NetworkMarker marker) { + PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender); + + invoker.invokePacketRecieving(event); + return event; + } + + public PlayerInjectionHandler getPlayerInjector() { + return new AbstractPlayerHandler(queuedFilters) { + private ChannelListener listener = NettyProtocolInjector.this; + + @Override + public void updatePlayer(Player player) { + // Ignore it + } + + @Override + public boolean uninjectPlayer(InetSocketAddress address) { + // Ignore this too + return true; + } + + @Override + public boolean uninjectPlayer(Player player) { + ChannelInjector.fromPlayer(player, listener).close(); + return true; + } + + @Override + public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { + ChannelInjector.fromPlayer(reciever, listener).sendServerPacket(packet.getHandle(), marker, filters); + } + + @Override + public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { + ChannelInjector.fromPlayer(player, listener).recieveClientPacket(mcPacket, null, true); + } + + @Override + public void injectPlayer(Player player, ConflictStrategy strategy) { + ChannelInjector.fromPlayer(player, listener).inject(); + } + + @Override + public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) { + // Ignore this + return null; + } + + @Override + public void handleDisconnect(Player player) { + ChannelInjector.fromPlayer(player, listener).close(); + } + }; + } + + /** + * Retrieve a view of this protocol injector as a packet injector. + * @return The packet injector. + */ + public PacketInjector getPacketInjector() { + return new AbstractPacketInjector(reveivedFilters) { + @Override + public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) { + NetworkMarker marker = buffered != null ? new NetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null; + ChannelInjector.fromPlayer(client, NettyProtocolInjector.this).saveMarker(packet.getHandle(), marker); + return packetReceived(packet, client, marker); + } + + @Override + public void inputBuffersChanged(Set set) { + bufferedPackets = set; + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java new file mode 100644 index 00000000..57f2e5f0 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolRegistry.java @@ -0,0 +1,129 @@ +package com.comphenix.protocol.injector.netty; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.PacketType.Protocol; +import com.comphenix.protocol.PacketType.Sender; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +/** + * Represents a way of accessing the new netty Protocol enum. + * @author Kristian + */ +// TODO: Handle modifications to the BiMap +public class NettyProtocolRegistry { + private Class enumProtocol; + + // The main lookup table + private BiMap> typeToClass = HashBiMap.create(); + private Set serverPackets = Sets.newHashSet(); + private Set clientPackets = Sets.newHashSet(); + + public NettyProtocolRegistry() { + enumProtocol = MinecraftReflection.getEnumProtocolClass(); + initialize(); + } + + /** + * Retrieve an immutable view of the packet type lookup. + * @return The packet type lookup. + */ + public Map> getPacketTypeLookup() { + return Collections.unmodifiableMap(typeToClass); + } + + /** + * Retrieve an immutable view of the class to packet tyåe lookup. + * @return The packet type lookup. + */ + public Map, PacketType> getPacketClassLookup() { + return Collections.unmodifiableMap(typeToClass.inverse()); + } + + /** + * Retrieve every known client packet, from every protocol. + * @return Every client packet. + */ + public Set getClientPackets() { + return Collections.unmodifiableSet(clientPackets); + } + + /** + * Retrieve every known server packet, from every protocol. + * @return Every server packet. + */ + public Set getServerPackets() { + return Collections.unmodifiableSet(serverPackets); + } + + /** + * Load the packet lookup tables in each protocol. + */ + private void initialize() { + final Object[] protocols = enumProtocol.getEnumConstants(); + List>> serverPackets = Lists.newArrayList(); + List>> clientPackets = Lists.newArrayList(); + StructureModifier modifier = null; + + for (Object protocol : protocols) { + if (modifier == null) + modifier = new StructureModifier(protocol.getClass().getSuperclass(), false); + StructureModifier>> maps = modifier.withTarget(protocol).withType(Map.class); + + serverPackets.add(maps.read(0)); + clientPackets.add(maps.read(1)); + } + + // Heuristic - there are more server packets than client packets + if (sum(clientPackets) > sum(serverPackets)) { + // Swap if this is violated + List>> temp = serverPackets; + serverPackets = clientPackets; + clientPackets = temp; + } + + for (int i = 0; i < protocols.length; i++) { + Enum enumProtocol = (Enum) protocols[i]; + Protocol equivalent = Protocol.fromVanilla(enumProtocol); + + // Associate known types + associatePackets(serverPackets.get(i), equivalent, Sender.SERVER); + associatePackets(clientPackets.get(i), equivalent, Sender.CLIENT); + } + } + + private void associatePackets(Map> lookup, Protocol protocol, Sender sender) { + for (Entry> entry : lookup.entrySet()) { + PacketType type = PacketType.fromCurrent(protocol, sender, entry.getKey(), PacketType.UNKNOWN_PACKET); + typeToClass.put(type, entry.getValue()); + + if (sender == Sender.SERVER) + serverPackets.add(type); + if (sender == Sender.CLIENT) + clientPackets.add(type); + } + } + + /** + * Retrieve the number of mapping in all the maps. + * @param maps - iterable of maps. + * @return The sum of all the entries. + */ + private int sum(Iterable>> maps) { + int count = 0; + + for (Map> map : maps) + count += map.size(); + return count; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettySocketAdaptor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettySocketAdaptor.java new file mode 100644 index 00000000..a772a860 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettySocketAdaptor.java @@ -0,0 +1,248 @@ +package com.comphenix.protocol.injector.netty; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; + +import net.minecraft.util.io.netty.channel.ChannelOption; +import net.minecraft.util.io.netty.channel.socket.SocketChannel; + +/** + * This class wraps a Netty {@link Channel} in a {@link Socket}. It overrides + * all methods in {@link Socket} to ensure that calls are not mistakingly made + * to the unsupported super socket. All operations that can be sanely applied to + * a {@link Channel} are implemented here. Those which cannot will throw an + * {@link UnsupportedOperationException}. + */ +// Thanks MD5. :) +class NettySocketAdaptor extends Socket { + private final SocketChannel ch; + + private NettySocketAdaptor(SocketChannel ch) { + this.ch = ch; + } + + public static NettySocketAdaptor adapt(SocketChannel ch) { + return new NettySocketAdaptor(ch); + } + + @Override + public void bind(SocketAddress bindpoint) throws IOException { + ch.bind(bindpoint).syncUninterruptibly(); + } + + @Override + public synchronized void close() throws IOException { + ch.close().syncUninterruptibly(); + } + + @Override + public void connect(SocketAddress endpoint) throws IOException { + ch.connect(endpoint).syncUninterruptibly(); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + ch.config().setConnectTimeoutMillis(timeout); + ch.connect(endpoint).syncUninterruptibly(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof NettySocketAdaptor && ch.equals(((NettySocketAdaptor) obj).ch); + } + + @Override + public java.nio.channels.SocketChannel getChannel() { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public InetAddress getInetAddress() { + return ch.remoteAddress().getAddress(); + } + + @Override + public InputStream getInputStream() throws IOException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return ch.config().getOption(ChannelOption.SO_KEEPALIVE); + } + + @Override + public InetAddress getLocalAddress() { + return ch.localAddress().getAddress(); + } + + @Override + public int getLocalPort() { + return ch.localAddress().getPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return ch.localAddress(); + } + + @Override + public boolean getOOBInline() throws SocketException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public OutputStream getOutputStream() throws IOException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public int getPort() { + return ch.remoteAddress().getPort(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return ch.config().getOption(ChannelOption.SO_RCVBUF); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return ch.remoteAddress(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return ch.config().getOption(ChannelOption.SO_REUSEADDR); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return ch.config().getOption(ChannelOption.SO_SNDBUF); + } + + @Override + public int getSoLinger() throws SocketException { + return ch.config().getOption(ChannelOption.SO_LINGER); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return ch.config().getOption(ChannelOption.TCP_NODELAY); + } + + @Override + public int getTrafficClass() throws SocketException { + return ch.config().getOption(ChannelOption.IP_TOS); + } + + @Override + public int hashCode() { + return ch.hashCode(); + } + + @Override + public boolean isBound() { + return ch.localAddress() != null; + } + + @Override + public boolean isClosed() { + return !ch.isOpen(); + } + + @Override + public boolean isConnected() { + return ch.isActive(); + } + + @Override + public boolean isInputShutdown() { + return ch.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return ch.isOutputShutdown(); + } + + @Override + public void sendUrgentData(int data) throws IOException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public void setKeepAlive(boolean on) throws SocketException { + ch.config().setOption(ChannelOption.SO_KEEPALIVE, on); + } + + @Override + public void setOOBInline(boolean on) throws SocketException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + ch.config().setOption(ChannelOption.SO_RCVBUF, size); + } + + @Override + public void setReuseAddress(boolean on) throws SocketException { + ch.config().setOption(ChannelOption.SO_REUSEADDR, on); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + ch.config().setOption(ChannelOption.SO_SNDBUF, size); + } + + @Override + public void setSoLinger(boolean on, int linger) throws SocketException { + ch.config().setOption(ChannelOption.SO_LINGER, linger); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + ch.config().setOption(ChannelOption.TCP_NODELAY, on); + } + + @Override + public void setTrafficClass(int tc) throws SocketException { + ch.config().setOption(ChannelOption.IP_TOS, tc); + } + + @Override + public void shutdownInput() throws IOException { + throw new UnsupportedOperationException("Operation not supported on Channel wrapper."); + } + + @Override + public void shutdownOutput() throws IOException { + ch.shutdownOutput().syncUninterruptibly(); + } + + @Override + public String toString() { + return ch.toString(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/LegacyPacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/LegacyPacketRegistry.java new file mode 100644 index 00000000..c56d8f5c --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/LegacyPacketRegistry.java @@ -0,0 +1,344 @@ +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.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; + +@SuppressWarnings("rawtypes") +class LegacyPacketRegistry { + private static final int MIN_SERVER_PACKETS = 5; + private static final int MIN_CLIENT_PACKETS = 5; + + // Fuzzy reflection + private FuzzyReflection packetRegistry; + + // The packet class to packet ID translator + private Map packetToID; + + // Packet IDs to classes, grouped by whether or not they're vanilla or custom defined + private Multimap customIdToPacket; + private Map vanillaIdToPacket; + + // Whether or not certain packets are sent by the client or the server + private ImmutableSet serverPackets; + private ImmutableSet clientPackets; + + // The underlying sets + private Set serverPacketsRef; + private Set clientPacketsRef; + + // New proxy values + private Map overwrittenPackets = new HashMap(); + + // Vanilla packets + private Map previousValues = new HashMap(); + + /** + * Initialize the registry. + */ + @SuppressWarnings({ "unchecked" }) + public void initialize() { + 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()); + } + }); + } + initializeSets(); + } + + @SuppressWarnings("unchecked") + private 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) + throw new InsufficientPacketsException("Insufficient server packets.", false, serverPackets.size()); + if (clientPackets.size() < MIN_CLIENT_PACKETS) + throw new InsufficientPacketsException("Insufficient client packets.", true, 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); + } + } + + /** + * Retrieve the packet mapping. + * @return The packet map. + */ + public Map getPacketToID() { + // Initialize it, if we haven't already + if (packetToID == null) { + initialize(); + } + return packetToID; + } + + private 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) { + throw new CannotCorrectTroveMapException(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 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 Map getOverwrittenPackets() { + return overwrittenPackets; + } + + /** + * Retrieve the vanilla classes handling each packet ID. + * @return Vanilla classes. + */ + public 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 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 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; + } + + /** + * Retrieves the correct packet class from a given packet ID. + * @param packetID - the packet ID. + * @return The associated class. + */ + public 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 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 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; + } + + /** + * Occurs when we were unable to retrieve all the packets in the registry. + * @author Kristian + */ + public static class InsufficientPacketsException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private final boolean client; + private final int packetCount; + + private InsufficientPacketsException(String message, boolean client, int packetCount) { + super(message); + this.client = client; + this.packetCount = packetCount; + } + + public boolean isClient() { + return client; + } + + public int getPacketCount() { + return packetCount; + } + } + + public static class CannotCorrectTroveMapException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private CannotCorrectTroveMapException(Throwable inner) { + super("Cannot correct trove map.", inner); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java index e08bd0ab..78e2cb8f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketInjector.java @@ -8,7 +8,7 @@ import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; /** - * Represents a incoming packet injector. + * Represents an incoming packet injector. * * @author Kristian */ 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 9448aff7..7758a873 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 @@ -17,36 +17,26 @@ package com.comphenix.protocol.injector.packet; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; +import java.util.Collections; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; -import javax.annotation.Nullable; - -import net.sf.cglib.proxy.Factory; - +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.netty.NettyProtocolRegistry; +import com.comphenix.protocol.injector.packet.LegacyPacketRegistry.CannotCorrectTroveMapException; +import com.comphenix.protocol.injector.packet.LegacyPacketRegistry.InsufficientPacketsException; 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; +import com.google.common.base.Function; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; /** * Static packet registry in Minecraft. - * * @author Kristian */ @SuppressWarnings("rawtypes") @@ -55,279 +45,351 @@ public class PacketRegistry { 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; + // Two different packet registry + private static volatile LegacyPacketRegistry LEGACY; + private static volatile NettyProtocolRegistry NETTY; - // The packet class to packet ID translator - private static Map packetToID; + // Cached for legacy + private static volatile Set NETTY_SERVER_PACKETS; + private static volatile Set NETTY_CLIENT_PACKETS; - // Packet IDs to classes, grouped by whether or not they're vanilla or custom defined - private static Multimap customIdToPacket; - private static Map vanillaIdToPacket; + // Cached for Netty + private static volatile Set LEGACY_SERVER_PACKETS; + private static volatile Set LEGACY_CLIENT_PACKETS; + private static volatile Map LEGACY_PREVIOUS_PACKETS; - // Whether or not certain packets are sent by the client or the server - private static ImmutableSet serverPackets; - private static ImmutableSet clientPackets; + // Whether or not the registry has + private static boolean INITIALIZED; - // 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()); - } - }); + /** + * Initialize the packet registry. + */ + private static void initialize() { + if (INITIALIZED) { + // Make sure they were initialized + if (NETTY == null && LEGACY == null) + throw new IllegalStateException("No initialized registry."); + return; } - 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); + // Check for netty + if (MinecraftReflection.isUsingNetty()) { + if (NETTY == null) { + NETTY = new NettyProtocolRegistry(); } - } catch (IllegalArgumentException e) { - // Whatever - ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, - Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e)); + } else { + initializeLegacy(); } - - // 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; + * Determine if the given packet type is supported on the current server. + * @param type - the type to check. + * @return TRUE if it is, FALSE otherwise. + */ + public static boolean isSupported(PacketType type) { + initialize(); + + if (NETTY != null) + return NETTY.getPacketTypeLookup().containsKey(type); + + // Look up the correct type + return type.isClient() ? + LEGACY.getClientPackets().contains(type.getLegacyId()) : + LEGACY.getServerPackets().contains(type.getLegacyId()); + } + + /** + * Initialize the legacy packet registry. + */ + private static void initializeLegacy() { + if (LEGACY == null) { + try { + LEGACY = new LegacyPacketRegistry(); + LEGACY.initialize(); + } catch (InsufficientPacketsException e) { + if (e.isClient()) { + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(e.getPacketCount()) + ); + } else { + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(e.getPacketCount()) + ); + } + } catch (CannotCorrectTroveMapException e) { + ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, + Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e.getCause())); + } + } + } + + /** + * Retrieve a map of every packet class to every ID. + *

+ * Deprecated: Use {@link #getPacketToType()} instead. + * @return A map of packet classes and their corresponding ID. + */ + @Deprecated + public static Map getPacketToID() { + initialize(); + + if (NETTY != null) { + @SuppressWarnings("unchecked") + Map result = (Map)Maps.transformValues(NETTY.getPacketClassLookup(), new Function() { + public Integer apply(PacketType type) { + return type.getLegacyId(); + }; + }); + return result; + } + return LEGACY.getPacketToID(); + } + + /** + * Retrieve a map of every packet class to the respective packet type. + * @return A map of packet classes and their corresponding packet type. + */ + public static Map getPacketToType() { + initialize(); + + if (NETTY != null) { + @SuppressWarnings("unchecked") + Map result = (Map)NETTY.getPacketClassLookup(); + return result; + } + return Maps.transformValues(LEGACY.getPacketToID(), new Function() { + public PacketType apply(Integer packetId) { + return PacketType.findLegacy(packetId); + }; + }); } /** * Retrieve the injected proxy classes handlig each packet ID. + *

+ * This is not supported in 1.7.2 and later. * @return Injected classes. */ + @Deprecated public static Map getOverwrittenPackets() { - return overwrittenPackets; + initialize(); + + if (LEGACY != null) + return LEGACY.getOverwrittenPackets(); + throw new IllegalStateException("Not supported on Netty."); } /** * Retrieve the vanilla classes handling each packet ID. * @return Vanilla classes. */ + @Deprecated public static Map getPreviousPackets() { - return previousValues; + initialize(); + + if (NETTY != null) { + // Construct it first + if (LEGACY_PREVIOUS_PACKETS == null) { + Map map = Maps.newHashMap(); + + for (Entry> entry : NETTY.getPacketTypeLookup().entrySet()) { + map.put(entry.getKey().getLegacyId(), entry.getValue()); + } + LEGACY_PREVIOUS_PACKETS = Collections.unmodifiableMap(map); + } + return LEGACY_PREVIOUS_PACKETS; + } + return LEGACY.getPreviousPackets(); } /** * Retrieve every known and supported server packet. + *

+ * Deprecated: Use {@link #getServerPacketTypes()} instead. * @return An immutable set of every known server packet. * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. */ + @Deprecated public static Set getServerPackets() throws FieldAccessException { - initializeSets(); + initialize(); - // 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; + if (NETTY != null) { + if (LEGACY_SERVER_PACKETS == null) { + LEGACY_SERVER_PACKETS = toLegacy(NETTY.getServerPackets()); + } + return LEGACY_SERVER_PACKETS; + } + return LEGACY.getServerPackets(); + } + + /** + * Retrieve every known and supported server packet type. + * @return Every server packet type. + */ + public static Set getServerPacketTypes() { + initialize(); + + if (NETTY != null) + return NETTY.getServerPackets(); + + // Handle legacy + if (NETTY_SERVER_PACKETS == null) { + NETTY_SERVER_PACKETS = toPacketTypes(LEGACY.getServerPackets()); + } + return NETTY_SERVER_PACKETS; } /** * Retrieve every known and supported client packet. + *

+ * Deprecated: Use {@link #getClientPacketTypes()} instead. * @return An immutable set of every known client packet. * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. */ + @Deprecated public static Set getClientPackets() throws FieldAccessException { - initializeSets(); + initialize(); - // As above - if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) - throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); - return clientPackets; + if (NETTY != null) { + if (LEGACY_CLIENT_PACKETS == null) { + LEGACY_CLIENT_PACKETS = toLegacy(NETTY.getClientPackets()); + } + return LEGACY_CLIENT_PACKETS; + } + return LEGACY.getClientPackets(); } - @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); + /** + * Retrieve every known and supported server packet type. + * @return Every server packet type. + */ + public static Set getClientPacketTypes() { + initialize(); + + if (NETTY != null) + return NETTY.getClientPackets(); + + // Handle legacy + if (NETTY_CLIENT_PACKETS == null) { + NETTY_CLIENT_PACKETS = toPacketTypes(LEGACY.getClientPackets()); } + return NETTY_CLIENT_PACKETS; + } + + /** + * Convert a set of packet types to a set of integers based on the legacy packet ID. + * @param types - packet type. + * @return Set of integers. + */ + private static Set toLegacy(Set types) { + Set result = Sets.newHashSet(); + + for (PacketType type : types) + result.add(type.getLegacyId()); + return Collections.unmodifiableSet(result); + } + + /** + * Convert a set of legacy packet IDs to packet types. + * @param types - legacy packet IDs. + * @return Set of packet types. + */ + private static Set toPacketTypes(Set ids) { + Set result = Sets.newHashSet(); + + for (int id : ids) + result.add(PacketType.findLegacy(id)); + return Collections.unmodifiableSet(result); } /** * Retrieves the correct packet class from a given packet ID. + *

+ * Deprecated: Use {@link #getPacketClassFromType(PacketType)} instead. * @param packetID - the packet ID. * @return The associated class. */ + @Deprecated public static Class getPacketClassFromID(int packetID) { - return getPacketClassFromID(packetID, false); + initialize(); + + if (NETTY != null) + return NETTY.getPacketTypeLookup().get(PacketType.findLegacy(packetID)); + return LEGACY.getPacketClassFromID(packetID); + } + + /** + * Retrieves the correct packet class from a given type. + * @param type - the packet type. + * @return The associated class. + */ + public static Class getPacketClassFromType(PacketType type) { + return getPacketClassFromType(type, false); + } + + /** + * Retrieves the correct packet class from a given type. + *

+ * Note that forceVanillla will be ignored on MC 1.7.2 and later. + * @param type - the packet type. + * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. + * @return The associated class. + */ + public static Class getPacketClassFromType(PacketType type, boolean forceVanilla) { + initialize(); + + if (NETTY != null) + return NETTY.getPacketTypeLookup().get(type); + return LEGACY.getPacketClassFromID(type.getLegacyId(), forceVanilla); } /** * Retrieves the correct packet class from a given packet ID. + *

+ * This method has been deprecated. * @param packetID - the packet ID. * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. * @return The associated class. */ + @Deprecated public static Class getPacketClassFromID(int packetID, boolean forceVanilla) { - Map lookup = forceVanilla ? previousValues : overwrittenPackets; - Class result = null; + initialize(); - // 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."); + if (LEGACY != null) + return LEGACY.getPacketClassFromID(packetID, forceVanilla); + return getPacketClassFromID(packetID); } /** * Retrieve the packet ID of a given packet. + *

+ * Deprecated: Use {@link #getPacketType(Class)}. * @param packet - the type of packet to check. - * @return The ID of the given packet. + * @return The legacy ID of the given packet. * @throws IllegalArgumentException If this is not a valid packet. */ + @Deprecated 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."); + initialize(); - // The registry contains both the overridden and original packets - return getPacketToID().get(packet); + if (NETTY != null) + return NETTY.getPacketClassLookup().get(packet).getLegacyId(); + return LEGACY.getPacketID(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. + * Retrieve the packet type of a given packet. + * @param packet - the class of the packet. + * @return The packet type. + * @throws IllegalArgumentException If this is not a valid packet. */ - 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(); - } - } + public static PacketType getPacketType(Class packet) { + initialize(); - return clazz; + if (NETTY != null) + return NETTY.getPacketClassLookup().get(packet); + return PacketType.findLegacy(LEGACY.getPacketID(packet)); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java new file mode 100644 index 00000000..052b4a4e --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPacketInjector.java @@ -0,0 +1,52 @@ +package com.comphenix.protocol.injector.spigot; + +import java.util.Set; + +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.injector.packet.PacketInjector; + +public abstract class AbstractPacketInjector implements PacketInjector { + private IntegerSet reveivedFilters; + + public AbstractPacketInjector(IntegerSet reveivedFilters) { + this.reveivedFilters = reveivedFilters; + } + + @Override + public boolean isCancelled(Object packet) { + // No, it's never cancelled + return false; + } + + @Override + public void setCancelled(Object packet, boolean cancelled) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addPacketHandler(int packetID) { + reveivedFilters.add(packetID); + return true; + } + + @Override + public boolean removePacketHandler(int packetID) { + reveivedFilters.remove(packetID); + return true; + } + + @Override + public boolean hasPacketHandler(int packetID) { + return reveivedFilters.contains(packetID); + } + + @Override + public Set getPacketHandlers() { + return reveivedFilters.toSet(); + } + + @Override + public void cleanupAll() { + reveivedFilters.clear(); + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java new file mode 100644 index 00000000..27d53afe --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/AbstractPlayerHandler.java @@ -0,0 +1,81 @@ +package com.comphenix.protocol.injector.spigot; + +import java.io.DataInputStream; +import java.util.Set; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler; + +public abstract class AbstractPlayerHandler implements PlayerInjectionHandler { + protected IntegerSet sendingFilters; + + public AbstractPlayerHandler(IntegerSet sendingFilters) { + this.sendingFilters = sendingFilters; + } + + @Override + public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public void setPlayerHook(PlayerInjectHooks playerHook) { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public void addPacketHandler(int packetID) { + sendingFilters.add(packetID); + } + + @Override + public void removePacketHandler(int packetID) { + sendingFilters.remove(packetID); + } + + @Override + public Set getSendingFilters() { + return sendingFilters.toSet(); + } + + @Override + public void close() { + sendingFilters.clear(); + } + + @Override + public PlayerInjectHooks getPlayerHook(GamePhase phase) { + return PlayerInjectHooks.NETWORK_SERVER_OBJECT; + } + + @Override + public boolean canRecievePackets() { + return true; + } + + @Override + public PlayerInjectHooks getPlayerHook() { + // Pretend that we do + return PlayerInjectHooks.NETWORK_SERVER_OBJECT; + } + + @Override + public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { + throw new UnsupportedOperationException("This is not needed in Spigot."); + } + + @Override + public void checkListener(PacketListener listener) { + // They're all fine! + } + + @Override + public void checkListener(Set listeners) { + // Yes, really + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java index 9870d184..ba3547e3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPacketInjector.java @@ -16,28 +16,15 @@ import com.google.common.collect.Sets; * * @author Kristian */ -class DummyPacketInjector implements PacketInjector { - private SpigotPacketInjector injector; - private IntegerSet reveivedFilters; - +class DummyPacketInjector extends AbstractPacketInjector implements PacketInjector { + private SpigotPacketInjector injector; private IntegerSet lastBufferedPackets = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1); public DummyPacketInjector(SpigotPacketInjector injector, IntegerSet reveivedFilters) { + super(reveivedFilters); this.injector = injector; - this.reveivedFilters = reveivedFilters; } - @Override - public boolean isCancelled(Object packet) { - // No, it's never cancelled - return false; - } - - @Override - public void setCancelled(Object packet, boolean cancelled) { - throw new UnsupportedOperationException(); - } - @Override public void inputBuffersChanged(Set set) { Set removed = Sets.difference(lastBufferedPackets.toSet(), set); @@ -52,35 +39,8 @@ class DummyPacketInjector implements PacketInjector { } } - @Override - public boolean addPacketHandler(int packetID) { - reveivedFilters.add(packetID); - return true; - } - - @Override - public boolean removePacketHandler(int packetID) { - reveivedFilters.remove(packetID); - return true; - } - - @Override - public boolean hasPacketHandler(int packetID) { - return reveivedFilters.contains(packetID); - } - - @Override - public Set getPacketHandlers() { - return reveivedFilters.toSet(); - } - @Override public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) { return injector.packetReceived(packet, client, buffered); } - - @Override - public void cleanupAll() { - reveivedFilters.clear(); - } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index 55ca43cf..dadf810c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -1,33 +1,25 @@ package com.comphenix.protocol.injector.spigot; -import java.io.DataInputStream; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; -import java.util.Set; import org.bukkit.entity.Player; import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.events.PacketListener; -import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler; /** * Dummy player handler that simply delegates to its parent Spigot packet injector. * * @author Kristian */ -class DummyPlayerHandler implements PlayerInjectionHandler { +class DummyPlayerHandler extends AbstractPlayerHandler { private SpigotPacketInjector injector; - private IntegerSet sendingFilters; - public DummyPlayerHandler(SpigotPacketInjector injector, IntegerSet sendingFilters) { + super(sendingFilters); this.injector = injector; - this.sendingFilters = sendingFilters; } @Override @@ -41,36 +33,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler { return true; } - @Override - public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - - @Override - public void setPlayerHook(PlayerInjectHooks playerHook) { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - - @Override - public void addPacketHandler(int packetID) { - sendingFilters.add(packetID); - } - - @Override - public void removePacketHandler(int packetID) { - sendingFilters.remove(packetID); - } - - @Override - public Set getSendingFilters() { - return sendingFilters.toSet(); - } - - @Override - public void close() { - sendingFilters.clear(); - } - @Override public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { injector.sendServerPacket(reciever, packet, marker, filters); @@ -92,16 +54,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler { // Just ignore } - @Override - public PlayerInjectHooks getPlayerHook(GamePhase phase) { - return PlayerInjectHooks.NETWORK_SERVER_OBJECT; - } - - @Override - public boolean canRecievePackets() { - return true; - } - @Override public PacketEvent handlePacketRecieved(PacketContainer packet, InputStream input, byte[] buffered) { // Associate this buffered data @@ -111,27 +63,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler { return null; } - @Override - public PlayerInjectHooks getPlayerHook() { - // Pretend that we do - return PlayerInjectHooks.NETWORK_SERVER_OBJECT; - } - - @Override - public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException { - throw new UnsupportedOperationException("This is not needed in Spigot."); - } - - @Override - public void checkListener(PacketListener listener) { - // They're all fine! - } - - @Override - public void checkListener(Set listeners) { - // Yes, really - } - @Override public void updatePlayer(Player player) { // Do nothing diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java index cbf8da58..04bf8991 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/FuzzyReflection.java @@ -19,6 +19,7 @@ package com.comphenix.protocol.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -28,6 +29,8 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import net.minecraft.util.com.google.common.collect.Sets; + import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -38,6 +41,33 @@ import com.google.common.collect.Maps; * @author Kristian */ public class FuzzyReflection { + /** + * Represents an interface for accessing a field. + * @author Kristian + */ + public interface FieldAccessor { + /** + * Retrieve the value of a field for a particular instance. + * @param instance - the instance, or NULL for a static field. + * @return The value of the field. + * @throws IllegalStateException If the current security context prohibits reflection. + */ + public Object get(Object instance); + } + + /** + * Represents an interface for invoking a method. + * @author Kristian + */ + public interface MethodAccessor { + /** + * Invoke the underlying method. + * @param target - the target instance, or NULL for a static method. + * @param args - the arguments to pass to the method. + * @return The return value, or NULL for void methods. + */ + public Object invoke(Object target, Object... args); + } // The class we're actually representing private Class source; @@ -88,12 +118,132 @@ public class FuzzyReflection { return new FuzzyReflection(reference.getClass(), forceAccess); } + /** + * Retrieve an accessor for the first field of the given type. + * @param instanceClass - the type of the instance to retrieve. + * @param fieldClass - type of the field to retrieve. + * @param forceAccess - whether or not to look for private and protected fields. + * @return The value of that field. + * @throws IllegalArgumentException If the field cannot be found. + */ + public static FieldAccessor getFieldAccessor(Class instanceClass, Class fieldClass, boolean forceAccess) { + // Get a field accessor + Field field = FuzzyReflection.fromObject(instanceClass, forceAccess).getFieldByType(null, fieldClass); + field.setAccessible(true); + return getFieldAccessor(field); + } + + /** + * Retrieve a field accessor from a given field that uses unchecked exceptions. + * @param field - the field. + * @return The field accessor. + */ + public static FieldAccessor getFieldAccessor(final Field field) { + return new FieldAccessor() { + @Override + public Object get(Object instance) { + try { + return field.get(instance); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot use reflection.", e); + } + } + }; + } + + /** + * Retrieve a method accessor for a method with the given name and signature. + * @param instanceClass - the parent class. + * @param name - the method name. + * @param parameters - the parameters. + * @return The method accessor. + */ + public static MethodAccessor getMethodAccessor(Class instanceClass, String name, Class... parameters) { + Method method = MethodUtils.getAccessibleMethod(instanceClass, name, parameters); + method.setAccessible(true); + return getMethodAccessor(method); + } + + /** + * Retrieve a method accessor for a particular method, avoding checked exceptions. + * @param method - the method to access. + * @return The method accessor. + */ + public static MethodAccessor getMethodAccessor(final Method method) { + return new MethodAccessor() { + @Override + public Object invoke(Object target, Object... args) { + try { + return method.invoke(target, args); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Cannot use reflection.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("An internal error occured.", e.getCause()); + } catch (IllegalArgumentException e) { + throw e; + } + } + }; + } + + /** + * Retrieve the value of the first field of the given type. + * @param instance - the instance to retrieve from. + * @param fieldClass - type of the field to retrieve. + * @param forceAccess - whether or not to look for private and protected fields. + * @return The value of that field. + * @throws IllegalArgumentException If the field cannot be found. + */ + public static T getFieldValue(Object instance, Class fieldClass, boolean forceAccess) { + @SuppressWarnings("unchecked") + T result = (T) getFieldAccessor(instance.getClass(), fieldClass, forceAccess).get(instance); + return result; + } + /** * Retrieves the underlying class. */ public Class getSource() { return source; } + + /** + * Retrieve the singleton instance of a class, from a method or field. + * @return The singleton instance. + * @throws IllegalStateException If the class has no singleton. + */ + public Object getSingleton() { + Method method = null; + Field field = null; + + try { + method = getMethodByParameters("getInstance", source.getClass(), new Class[0]); + } catch (IllegalArgumentException e) { + // Try getting the field instead + // Note that this will throw an exception if not found + field = getFieldByType("instance", source.getClass()); + } + + // Convert into unchecked exceptions + if (method != null) { + try { + method.setAccessible(true); + return method.invoke(null); + } catch (Exception e) { + throw new RuntimeException("Cannot invoke singleton method " + method, e); + } + } + if (field != null) { + try { + field.setAccessible(true); + return field.get(null); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get content of singleton field " + field, e); + } + } + // We should never get to this point + throw new IllegalStateException("Impossible."); + } /** * Retrieve the first method that matches. @@ -246,7 +396,6 @@ public class FuzzyReflection { methods.add(method); } } - return methods; } @@ -473,6 +622,25 @@ public class FuzzyReflection { return setUnion(source.getFields()); } + /** + * Retrieves all private and public fields, up until a certain superclass. + * @param excludeClass - the class (and its superclasses) to exclude from the search. + * @return Every such declared field. + */ + public Set getDeclaredFields(Class excludeClass) { + if (forceAccess) { + Class current = source; + Set fields = Sets.newLinkedHashSet(); + + while (current != null && current != excludeClass) { + fields.addAll(Arrays.asList(current.getDeclaredFields())); + current = current.getSuperclass(); + } + return fields; + } + return getFields(); + } + /** * Retrieves all private and public methods in declared order (after JDK 1.5). *

@@ -509,7 +677,6 @@ public class FuzzyReflection { result.add(element); } } - return result; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectEnum.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectEnum.java index 9499454b..09a05543 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectEnum.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectEnum.java @@ -18,7 +18,9 @@ package com.comphenix.protocol.reflect; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import com.google.common.collect.BiMap; @@ -31,27 +33,32 @@ import com.google.common.collect.HashBiMap; * want to prevent the creation of additional members dynamically. * @author Kristian */ -public class ObjectEnum { +public class ObjectEnum implements Iterable { // Used to convert between IDs and names protected BiMap members = HashBiMap.create(); /** * Registers every declared integer field. */ - public ObjectEnum() { - registerAll(); + public ObjectEnum(Class fieldType) { + registerAll(fieldType); } /** - * Registers every public int field as a member. + * Registers every public assignable static field as a member. */ @SuppressWarnings("unchecked") - protected void registerAll() { + protected void registerAll(Class fieldType) { try { // Register every int field for (Field entry : this.getClass().getFields()) { - if (entry.getType().equals(int.class)) { - registerMember((T) entry.get(this), entry.getName()); + if (Modifier.isStatic(entry.getModifiers()) && fieldType.isAssignableFrom(entry.getType())) { + T value = (T) entry.get(null); + + if (value == null) + throw new IllegalArgumentException("Field " + entry + " was NULL. Remember to " + + "construct the object after the field has been declared."); + registerMember(value, entry.getName()); } } @@ -63,13 +70,17 @@ public class ObjectEnum { } /** - * Registers a member. + * Registers a member if its not present. * @param instance - member instance. * @param name - name of member. + * @return TRUE if the member was registered, FALSE otherwise. */ - protected void registerMember(T instance, String name) { - members.put(instance, name); - + public boolean registerMember(T instance, String name) { + if (!members.containsKey(instance)) { + members.put(instance, name); + return true; + } + return false; } /** @@ -106,4 +117,9 @@ public class ObjectEnum { public Set values() { return new HashSet(members.keySet()); } + + @Override + public Iterator iterator() { + return members.keySet().iterator(); + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index 664e8ea9..48794997 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -581,7 +581,7 @@ public class StructureModifier { List result = new ArrayList(); // Retrieve every private and public field - for (Field field : FuzzyReflection.fromClass(type, true).getFields()) { + for (Field field : FuzzyReflection.fromClass(type, true).getDeclaredFields(superclassExclude)) { int mod = field.getModifiers(); // Ignore static and "abstract packet" fields @@ -595,6 +595,4 @@ public class StructureModifier { return result; } - - } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java index 19874b5a..2ae4bc1e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/AggregateCloner.java @@ -256,7 +256,7 @@ public class AggregateCloner implements Cloner { if (index < cloners.size()) { return cloners.get(index).clone(source); } - + // Damn - failure throw new IllegalArgumentException("Cannot clone " + source + ": No cloner is sutable."); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java index 143e9d04..103a4ae1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/cloning/ImmutableDetector.java @@ -72,7 +72,7 @@ public class ImmutableDetector implements Cloner { if (Primitives.isWrapperType(type) || String.class.equals(type)) return true; // May not be true, but if so, that kind of code is broken anyways - if (type.isEnum()) + if (isEnumWorkaround(type)) return true; for (Class clazz : immutableClasses) @@ -83,6 +83,16 @@ public class ImmutableDetector implements Cloner { return false; } + // This is just great. Just great. + private static boolean isEnumWorkaround(Class enumClass) { + while (enumClass != null) { + if (enumClass.isEnum()) + return true; + enumClass = enumClass.getSuperclass(); + } + return false; + } + @Override public Object clone(Object source) { // Safe if the class is immutable diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java new file mode 100644 index 00000000..a24ada26 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftFields.java @@ -0,0 +1,56 @@ +package com.comphenix.protocol.utility; + +import org.bukkit.entity.Player; + +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.FuzzyReflection.FieldAccessor; + +/** + * Retrieve the content of well-known fields in Minecraft. + * @author Kristian + */ +public class MinecraftFields { + // Cached accessors + private static volatile FieldAccessor CONNECTION_ACCESSOR; + private static volatile FieldAccessor NETWORK_ACCESSOR; + + private MinecraftFields() { + // Not constructable + } + + /** + * Retrieve the network mananger associated with a particular player. + * @param player - the player. + * @return The network manager. + */ + public static Object getNetworkManager(Player player) { + Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player); + + if (NETWORK_ACCESSOR == null) { + Class networkClass = MinecraftReflection.getNetworkManagerClass(); + Class connectionClass = MinecraftReflection.getNetServerHandlerClass(); + NETWORK_ACCESSOR = FuzzyReflection.getFieldAccessor(connectionClass, networkClass, true); + } + // Retrieve the network manager + return NETWORK_ACCESSOR.get(getPlayerConnection(nmsPlayer)); + } + + /** + * Retrieve the player connection (or NetServerHandler) associated with a player. + * @param player - the player. + * @return The player connection. + */ + public static Object getPlayerConnection(Player player) { + return getPlayerConnection(BukkitUnwrapper.getInstance().unwrapItem(player)); + } + + // Retrieve player connection from a native instance + private static Object getPlayerConnection(Object nmsPlayer) { + if (CONNECTION_ACCESSOR == null) { + Class connectionClass = MinecraftReflection.getNetServerHandlerClass(); + CONNECTION_ACCESSOR = FuzzyReflection.getFieldAccessor(nmsPlayer.getClass(), connectionClass, true); + } + return CONNECTION_ACCESSOR.get(nmsPlayer); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java index f5706fa4..5cccb5ed 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java @@ -1,8 +1,24 @@ package com.comphenix.protocol.utility; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; import java.util.Map; +import org.bukkit.command.defaults.EnchantCommand; + +import net.minecraft.util.io.netty.buffer.ByteBuf; +import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator; +import net.minecraft.util.io.netty.channel.ChannelHandlerContext; +import net.minecraft.util.io.netty.util.concurrent.GenericFutureListener; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.FuzzyReflection; /** @@ -14,6 +30,14 @@ public class MinecraftMethods { // For player connection private volatile static Method sendPacketMethod; + // For network manager + private volatile static Method networkManagerHandle; + private volatile static Method networkManagerPacketRead; + + // For packet + private volatile static Method packetReadByteBuf; + private volatile static Method packetWriteByteBuf; + /** * Retrieve the send packet method in PlayerConnection/NetServerHandler. * @return The send packet method. @@ -47,6 +71,50 @@ public class MinecraftMethods { return sendPacketMethod; } + /** + * Retrieve the disconnect method for a given player connection. + * @param playerConnection - the player connection. + * @return The + */ + public static Method getDisconnectMethod(Class playerConnection) { + try { + return FuzzyReflection.fromClass(playerConnection).getMethodByName("disconnect.*"); + } catch (IllegalArgumentException e) { + // Just assume it's the first String method + return FuzzyReflection.fromObject(playerConnection).getMethodByParameters("disconnect", String.class); + } + } + + /** + * Retrieve the handle(Packet, GenericFutureListener[]) method of network manager. + *

+ * This only exists in version 1.7.2 and above. + * @return The handle method. + */ + public static Method getNetworkManagerHandleMethod() { + if (networkManagerHandle == null) { + networkManagerHandle = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true). + getMethodByParameters("handle", MinecraftReflection.getPacketClass(), GenericFutureListener[].class); + networkManagerHandle.setAccessible(true); + } + return networkManagerHandle; + } + + /** + * Retrieve the packetRead(ChannelHandlerContext, Packet) method of NetworkMananger. + *

+ * This only exists in version 1.7.2 and above. + * @return The packetRead method. + */ + public static Method getNetworkManagerReadPacketMethod() { + if (networkManagerPacketRead == null) { + networkManagerPacketRead = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true). + getMethodByParameters("packetRead", ChannelHandlerContext.class, MinecraftReflection.getPacketClass()); + networkManagerPacketRead.setAccessible(true); + } + return networkManagerPacketRead; + } + /** * Retrieve a method mapped list of every method with the given signature. * @param source - class source. @@ -60,5 +128,105 @@ public class MinecraftMethods { reflect.getMethodListByParameters(Void.TYPE, params) ); } + + /** + * Retrieve the Packet.read(PacketDataSerializer) method. + *

+ * This only exists in version 1.7.2 and above. + * @return The packet read method. + */ + public static Method getPacketReadByteBufMethod() { + initializePacket(); + return packetReadByteBuf; + } + /** + * Retrieve the Packet.write(PacketDataSerializer) method. + *

+ * This only exists in version 1.7.2 and above. + * @return The packet write method. + */ + public static Method getPacketWriteByteBufMethod() { + initializePacket(); + return packetWriteByteBuf; + } + + /** + * Initialize the two read() and write() methods. + */ + private static void initializePacket() { + // Initialize the methods + if (packetReadByteBuf == null || packetWriteByteBuf == null) { + // This object will allow us to detect which methods were called + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(MinecraftReflection.getPacketDataSerializerClass()); + enhancer.setCallback(new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) + throws Throwable { + if (method.getName().contains("read")) + throw new ReadMethodException(); + if (method.getName().contains("write")) + throw new WriteMethodException(); + return proxy.invokeSuper(obj, args); + } + }); + + // Create our proxy object + Object javaProxy = enhancer.create( + new Class[] { ByteBuf.class }, + new Object[] { UnpooledByteBufAllocator.DEFAULT.buffer() } + ); + + Object lookPacket = new PacketContainer(PacketType.Play.Client.PLACE).getHandle(); + List candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). + getMethodListByParameters(Void.TYPE, new Class[] { MinecraftReflection.getPacketDataSerializerClass() }); + + // Look through all the methods + for (Method method : candidates) { + try { + method.invoke(lookPacket, javaProxy); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof ReadMethodException) + // Must be the reader + packetReadByteBuf = method; + else if (e.getCause() instanceof WriteMethodException) + packetWriteByteBuf = method; + else + throw new RuntimeException("Inner exception.", e); + } catch (Exception e) { + throw new RuntimeException("Generic reflection error.", e); + } + } + + if (packetReadByteBuf == null) + throw new IllegalStateException("Unable to find Packet.read(PacketDataSerializer)"); + if (packetWriteByteBuf == null) + throw new IllegalStateException("Unable to find Packet.write(PacketDataSerializer)"); + } + } + + /** + * An internal exception used to detect read methods. + * @author Kristian + */ + private static class ReadMethodException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ReadMethodException() { + super("A read method was executed."); + } + } + + /** + * An internal exception used to detect write methods. + * @author Kristian + */ + private static class WriteMethodException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public WriteMethodException() { + super("A write method was executed."); + } + } } 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 4e9e6d59..f611835a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -35,6 +35,7 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; +import net.minecraft.util.io.netty.buffer.ByteBuf; import net.sf.cglib.asm.ClassReader; import net.sf.cglib.asm.MethodVisitor; import net.sf.cglib.asm.Opcodes; @@ -43,7 +44,7 @@ import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.inventory.ItemStack; -import com.comphenix.protocol.Packets; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; @@ -118,7 +119,8 @@ public class MinecraftReflection { // New in 1.4.5 private static Method craftNMSMethod; - private static Method craftBukkitMethod; + private static Method craftBukkitNMS; + private static Method craftBukkitOBC; private static boolean craftItemStackFailed; // The NMS version @@ -135,6 +137,9 @@ public class MinecraftReflection { */ private static boolean initializing; + // Whether or not we are using netty + private static Boolean cachedNetty; + private MinecraftReflection() { // No need to make this constructable. } @@ -602,6 +607,23 @@ public class MinecraftReflection { } } + /** + * Determine if this Minecraft version is using Netty. + *

+ * Spigot is ignored in this consideration. + * @return TRUE if it does, FALSE otherwise. + */ + public static boolean isUsingNetty() { + if (cachedNetty == null) { + try { + cachedNetty = getEnumProtocolClass() != null; + } catch (RuntimeException e) { + cachedNetty = false; + } + } + return cachedNetty; + } + /** * Retrieve the least derived class, except Object. * @return Least derived super class. @@ -1045,7 +1067,7 @@ public class MinecraftReflection { try { return getMinecraftClass("AttributeSnapshot"); } catch (RuntimeException e) { - final Class packetUpdateAttributes = PacketRegistry.getPacketClassFromID(44, true); + final Class packetUpdateAttributes = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.UPDATE_ATTRIBUTES, true); final String packetSignature = packetUpdateAttributes.getCanonicalName().replace('.', '/'); // HACK - class is found by inspecting code @@ -1147,7 +1169,7 @@ public class MinecraftReflection { return getMinecraftClass("MobEffect"); } catch (RuntimeException e) { // It is the second parameter in Packet41MobEffect - Class packet = PacketRegistry.getPacketClassFromID(Packets.Server.MOB_EFFECT); + Class packet = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.MOB_EFFECT); Constructor constructor = FuzzyReflection.fromClass(packet).getConstructor( FuzzyMethodContract.newBuilder(). parameterCount(2). @@ -1159,6 +1181,41 @@ public class MinecraftReflection { } } + /** + * Retrieve the packet data serializer class that overrides ByteBuf. + * @return The data serializer class. + */ + public static Class getPacketDataSerializerClass() { + try { + return getMinecraftClass("PacketDataSerializer"); + } catch (RuntimeException e) { + Class packet = getPacketClass(); + Method method = FuzzyReflection.fromClass(packet).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterDerivedOf(ByteBuf.class). + returnTypeVoid(). + build() + ); + return setMinecraftClass("PacketDataSerializer", method.getParameterTypes()[0]); + } + } + + /** + * Retrieve an instance of the packet data serializer wrapper. + * @param buffer - the buffer. + * @return The instance. + */ + public static ByteBuf getPacketDataSerializer(ByteBuf buffer) { + Class packetSerializer = getPacketDataSerializerClass(); + + try { + return (ByteBuf) packetSerializer.getConstructor(ByteBuf.class).newInstance(buffer); + } catch (Exception e) { + throw new RuntimeException("Cannot construct packet serializer.", e); + } + } + /** * Determine if a given method retrieved by ASM is a constructor. * @param name - the name of the method. @@ -1219,7 +1276,7 @@ public class MinecraftReflection { */ public static ItemStack getBukkitItemStack(ItemStack bukkitItemStack) { // Delegate this task to the method that can execute it - if (craftBukkitMethod != null) + if (craftBukkitNMS != null) return getBukkitItemByMethod(bukkitItemStack); if (craftBukkitConstructor == null) { @@ -1243,9 +1300,10 @@ public class MinecraftReflection { } private static ItemStack getBukkitItemByMethod(ItemStack bukkitItemStack) { - if (craftBukkitMethod == null) { + if (craftBukkitNMS == null) { try { - craftBukkitMethod = getCraftItemStackClass().getMethod("asCraftCopy", ItemStack.class); + craftBukkitNMS = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class); + craftBukkitOBC = getCraftItemStackClass().getMethod("asCraftMirror", MinecraftReflection.getItemStackClass()); } catch (Exception e) { craftItemStackFailed = true; throw new RuntimeException("Cannot find CraftItemStack.asCraftCopy(org.bukkit.inventory.ItemStack).", e); @@ -1254,7 +1312,8 @@ public class MinecraftReflection { // Next, construct it try { - return (ItemStack) craftBukkitMethod.invoke(null, bukkitItemStack); + Object nmsItemStack = craftBukkitNMS.invoke(null, bukkitItemStack); + return (ItemStack) craftBukkitOBC.invoke(null, nmsItemStack); } catch (Exception e) { throw new RuntimeException("Cannot construct CraftItemStack.", e); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java index 24f33e54..ab63fe34 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java @@ -17,6 +17,7 @@ package com.comphenix.protocol.utility; +import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.regex.Matcher; @@ -34,7 +35,9 @@ import com.google.common.collect.Ordering; * * @author Kristian */ -public class MinecraftVersion implements Comparable { +public class MinecraftVersion implements Comparable, Serializable { + private static final long serialVersionUID = 1L; + /** * Regular expression used to parse version strings. */ diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java index 8c1708e8..d23aba23 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/SnapshotVersion.java @@ -1,7 +1,9 @@ package com.comphenix.protocol.utility; +import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.regex.Matcher; @@ -14,12 +16,16 @@ import com.google.common.collect.ComparisonChain; * Used to parse a snapshot version. * @author Kristian */ -public class SnapshotVersion implements Comparable { +public class SnapshotVersion implements Comparable, Serializable { + // Increment when the class changes + private static final long serialVersionUID = 1L; + private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(\\d{2}w\\d{2})([a-z])"); - private final String rawString; private final Date snapshotDate; private final int snapshotWeekVersion; + + private transient String rawString; public SnapshotVersion(String version) { Matcher matcher = SNAPSHOT_PATTERN.matcher(version.trim()); @@ -70,6 +76,15 @@ public class SnapshotVersion implements Comparable { * @return The snapshot string. */ public String getSnapshotString() { + if (rawString == null) { + // It's essential that we use the same locale + Calendar current = Calendar.getInstance(Locale.US); + current.setTime(snapshotDate); + rawString = String.format("%02dw%02d%s", + current.get(Calendar.YEAR) % 100, + current.get(Calendar.WEEK_OF_YEAR), + (char) ((int)'a' + snapshotWeekVersion)); + } return rawString; } @@ -103,6 +118,6 @@ public class SnapshotVersion implements Comparable { @Override public String toString() { - return rawString; + return getSnapshotString(); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java index e0af5eaf..9854ce49 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java @@ -163,7 +163,8 @@ public class ChunkPosition { // Construct the underlying ChunkPosition try { - return chunkPositionConstructor.newInstance(specific.x, specific.y, specific.z); + Object result = chunkPositionConstructor.newInstance(specific.x, specific.y, specific.z); + return result; } catch (Exception e) { throw new RuntimeException("Cannot construct ChunkPosition.", e); } @@ -183,7 +184,8 @@ public class ChunkPosition { if (intModifier.size() >= 3) { try { StructureModifier instance = intModifier.withTarget(generic); - return new ChunkPosition(instance.read(0), instance.read(1), instance.read(2)); + ChunkPosition result = new ChunkPosition(instance.read(0), instance.read(1), instance.read(2)); + return result; } catch (FieldAccessException e) { // This is an exeptional work-around, so we don't want to burden the caller with the messy details throw new RuntimeException("Field access error.", e); @@ -224,6 +226,6 @@ public class ChunkPosition { @Override public String toString() { - return "ChunkPosition [x=" + x + ", y=" + y + ", z=" + z + "]"; + return "WrappedChunkPosition [x=" + x + ", y=" + y + ", z=" + z + "]"; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index f5aad81d..8cfdb1d2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -17,6 +17,7 @@ package com.comphenix.protocol.wrappers; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -52,7 +53,6 @@ import com.google.common.collect.Iterators; * @author Kristian */ public class WrappedDataWatcher implements Iterable { - /** * Used to assign integer IDs to given types. */ @@ -61,12 +61,16 @@ public class WrappedDataWatcher implements Iterable { // Fields private static Field valueMapField; private static Field readWriteLockField; + private static Field entityField; // Methods private static Method createKeyValueMethod; private static Method updateKeyValueMethod; private static Method getKeyValueMethod; + // Constructors + private static Constructor createDataWatcherConstructor; + // Entity methods private volatile static Field entityDataField; @@ -94,7 +98,11 @@ public class WrappedDataWatcher implements Iterable { public WrappedDataWatcher() { // Just create a new watcher try { - this.handle = MinecraftReflection.getDataWatcherClass().newInstance(); + if (MinecraftReflection.isUsingNetty()) { + this.handle = newEntityHandle(null); + } else { + this.handle = MinecraftReflection.getDataWatcherClass().newInstance(); + } initialize(); } catch (Exception e) { @@ -117,6 +125,42 @@ public class WrappedDataWatcher implements Iterable { initialize(); } + /** + * Construct a new data watcher with the given entity. + *

+ * In 1.6.4 and ealier, this will fall back to using {@link #WrappedDataWatcher()}. + * @param entity - the entity. + * @return The wrapped data watcher. + */ + public static WrappedDataWatcher newWithEntity(Entity entity) { + // Use the old constructor + if (!MinecraftReflection.isUsingNetty()) + return new WrappedDataWatcher(); + return new WrappedDataWatcher(newEntityHandle(entity)); + } + + /** + * Construct a new native DataWatcher with the given entity. + *

+ * Warning: This is only supported in 1.7.2 and above. + * @param entity - the entity, or NULL. + * @return The data watcher. + */ + private static Object newEntityHandle(Entity entity) { + Class dataWatcher = MinecraftReflection.getDataWatcherClass(); + + try { + if (createDataWatcherConstructor == null) + createDataWatcherConstructor = dataWatcher.getConstructor(MinecraftReflection.getEntityClass()); + + return createDataWatcherConstructor.newInstance( + BukkitUnwrapper.getInstance().unwrapItem(entity) + ); + } catch (Exception e) { + throw new RuntimeException("Cannot construct data watcher.", e); + } + } + /** * Create a new data watcher for a list of watchable objects. *

@@ -548,6 +592,11 @@ public class WrappedDataWatcher implements Iterable { // It's not a big deal } + // Check for the entity field as well + if (MinecraftReflection.isUsingNetty()) { + entityField = fuzzy.getFieldByType("entity", MinecraftReflection.getEntityClass()); + entityField.setAccessible(true); + } initializeMethods(fuzzy); } @@ -648,4 +697,38 @@ public class WrappedDataWatcher implements Iterable { public String toString() { return asMap().toString(); } + + /** + * Retrieve the entity associated with this data watcher. + *

+ * Warning: This is only supported on 1.7.2 and above. + * @return The entity, or NULL. + */ + public Entity getEntity() { + if (!MinecraftReflection.isUsingNetty()) + throw new IllegalStateException("This method is only supported on 1.7.2 and above."); + + try { + return (Entity) MinecraftReflection.getBukkitEntity(entityField.get(handle)); + } catch (Exception e) { + throw new RuntimeException("Unable to retrieve entity.", e); + } + } + + /** + * Set the entity associated with this data watcher. + *

+ * Warning: This is only supported on 1.7.2 and above. + * @param entity - the new entity. + */ + public void setEntity(Entity entity) { + if (!MinecraftReflection.isUsingNetty()) + throw new IllegalStateException("This method is only supported on 1.7.2 and above."); + + try { + entityField.set(handle, BukkitUnwrapper.getInstance().unwrapItem(entity)); + } catch (Exception e) { + throw new RuntimeException("Unable to set entity.", e); + } + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/collection/ConvertedMap.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/collection/ConvertedMap.java index 9a452306..1262c31b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/collection/ConvertedMap.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/collection/ConvertedMap.java @@ -22,6 +22,10 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; /** * Represents a map that wraps another map by transforming the entries going in and out. @@ -95,12 +99,13 @@ public abstract class ConvertedMap extends AbstractConverte @Override public VOuter getValue() { - return ConvertedMap.this.toOuter(inner.getValue()); + return ConvertedMap.this.toOuter(inner.getKey(), inner.getValue()); } @Override public VOuter setValue(VOuter value) { - return ConvertedMap.this.toOuter(inner.setValue(ConvertedMap.this.toInner(value))); + final VInner converted = ConvertedMap.this.toInner(getKey(), value); + return ConvertedMap.this.toOuter(getKey(), inner.setValue(converted)); } @Override @@ -112,9 +117,28 @@ public abstract class ConvertedMap extends AbstractConverte }; } + /** + * Convert a value from the inner map to the outer visible map. + * @param inner - the inner value. + * @return The outer value. + */ + protected VOuter toOuter(Key key, VInner inner) { + return toOuter(inner); + } + + /** + * Convert a value from the outer map to the internal inner map. + * @param outer - the outer value. + * @return The inner value. + */ + protected VInner toInner(Key key, VOuter outer) { + return toInner(outer); + } + + @SuppressWarnings("unchecked") @Override public VOuter get(Object key) { - return toOuter(inner.get(key)); + return toOuter((Key) key, inner.get(key)); } @Override @@ -129,7 +153,7 @@ public abstract class ConvertedMap extends AbstractConverte @Override public VOuter put(Key key, VOuter value) { - return toOuter(inner.put(key, toInner(value))); + return toOuter(key, inner.put(key, toInner(key, value))); } @Override @@ -139,9 +163,10 @@ public abstract class ConvertedMap extends AbstractConverte } } + @SuppressWarnings("unchecked") @Override public VOuter remove(Object key) { - return toOuter(inner.remove(key)); + return toOuter((Key) key, inner.remove(key)); } @Override @@ -151,17 +176,12 @@ public abstract class ConvertedMap extends AbstractConverte @Override public Collection values() { - return new ConvertedCollection(inner.values()) { + return Collections2.transform(entrySet(), new Function, VOuter>() { @Override - protected VOuter toOuter(VInner inner) { - return ConvertedMap.this.toOuter(inner); + public VOuter apply(@Nullable java.util.Map.Entry entry) { + return entry.getValue(); } - - @Override - protected VInner toInner(VOuter outer) { - return ConvertedMap.this.toInner(outer); - } - }; + }); } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NameProperty.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NameProperty.java new file mode 100644 index 00000000..645be3c6 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NameProperty.java @@ -0,0 +1,94 @@ +package com.comphenix.protocol.wrappers.nbt; + +import java.util.Map; + +import net.minecraft.util.com.google.common.collect.Maps; + +import com.comphenix.protocol.reflect.StructureModifier; + +public abstract class NameProperty { + private static final Map, StructureModifier> MODIFIERS = Maps.newConcurrentMap(); + + /** + * Retrieve the name. + * @return The name. + */ + public abstract String getName(); + + /** + * Set the name. + * @param name - the new value of the name. + */ + public abstract void setName(String name); + + /** + * Retrieve the string modifier for a particular class. + * @param baseClass - the base class. + * @return The string modifier, with no target. + */ + private static StructureModifier getModifier(Class baseClass) { + StructureModifier modifier = MODIFIERS.get(baseClass); + + // Share modifier + if (modifier == null) { + modifier = new StructureModifier(baseClass, Object.class, false).withType(String.class); + MODIFIERS.put(baseClass, modifier); + } + return modifier; + } + + /** + * Determine if a string of the given index exists in the base class. + * @param baseClass - the base class. + * @param index - the index to check. + * @return TRUE if it does, FALSE otherwise. + */ + public static boolean hasStringIndex(Class baseClass, int index) { + if (index < 0) + return false; + return index < getModifier(baseClass).size(); + } + + /** + * Retrieve a name property that delegates all read and write operations to a field of the given target. + * @param baseClass - the base class. + * @param target - the target + * @param index - the index of the field. + * @return The name property. + */ + public static NameProperty fromStringIndex(Class baseClass, Object target, final int index) { + final StructureModifier modifier = getModifier(baseClass).withTarget(target); + + return new NameProperty() { + @Override + public String getName() { + return modifier.read(index); + } + + @Override + public void setName(String name) { + modifier.write(index, name); + } + }; + } + + /** + * Retrieve a new name property around a simple field, forming a Java bean. + * @return The name property. + */ + public static NameProperty fromBean() { + return new NameProperty() { + private String name; + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java index 917b7c09..e6ab163a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/NbtFactory.java @@ -41,6 +41,7 @@ import com.comphenix.protocol.wrappers.BukkitConverters; public class NbtFactory { // Used to create the underlying tag private static Method methodCreateTag; + private static boolean methodCreateWithName; // Item stack trickery private static StructureModifier itemStackModifier; @@ -188,10 +189,13 @@ public class NbtFactory { /** * Initialize a NBT wrapper. + *

+ * Use {@link #fromNMS(Object, String)} instead. * @param handle - the underlying net.minecraft.server object to wrap. * @return A NBT wrapper. */ @SuppressWarnings({"unchecked", "rawtypes"}) + @Deprecated public static NbtWrapper fromNMS(Object handle) { WrappedElement partial = new WrappedElement(handle); @@ -203,6 +207,25 @@ public class NbtFactory { else return partial; } + + /** + * Initialize a NBT wrapper with a name. + * @param name - the name of the tag, or NULL if not valid. + * @param handle - the underlying net.minecraft.server object to wrap. + * @return A NBT wrapper. + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static NbtWrapper fromNMS(Object handle, String name) { + WrappedElement partial = new WrappedElement(handle, name); + + // See if this is actually a compound tag + if (partial.getType() == NbtType.TAG_COMPOUND) + return (NbtWrapper) new WrappedCompound(handle, name); + else if (partial.getType() == NbtType.TAG_LIST) + return new WrappedList(handle, name); + else + return partial; + } /** * Retrieve the NBT compound from a given NMS handle. @@ -351,7 +374,6 @@ public class NbtFactory { * @return The new wrapped NBT tag. * @throws FieldAccessException If we're unable to create the underlying tag. */ - @SuppressWarnings({"unchecked", "rawtypes"}) public static NbtWrapper ofWrapper(NbtType type, String name) { if (type == null) throw new IllegalArgumentException("type cannot be NULL."); @@ -362,19 +384,22 @@ public class NbtFactory { Class base = MinecraftReflection.getNBTBaseClass(); // Use the base class - methodCreateTag = FuzzyReflection.fromClass(base). - getMethodByParameters("createTag", base, new Class[] { byte.class, String.class }); + try { + methodCreateTag = findCreateMethod(base, byte.class, String.class); + methodCreateWithName = true; + + } catch (Exception e) { + methodCreateTag = findCreateMethod(base, byte.class); + methodCreateWithName = false; + } } try { - Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name); - - if (type == NbtType.TAG_COMPOUND) - return (NbtWrapper) new WrappedCompound(handle); - else if (type == NbtType.TAG_LIST) - return (NbtWrapper) new WrappedList(handle); + // Delegate to the correct version + if (methodCreateWithName) + return createTagWithName(type, name); else - return new WrappedElement(handle); + return createTagSetName(type, name); } catch (Exception e) { // Inform the caller @@ -383,6 +408,43 @@ public class NbtFactory { e); } } + + /** + * Find the create method of NBTBase. + * @param base - the base NBT. + * @param params - the parameters. + */ + private static Method findCreateMethod(Class base, Class... params) { + Method method = FuzzyReflection.fromClass(base, true).getMethodByParameters("createTag", base, params); + method.setAccessible(true); + return method; + } + + // For Minecraft 1.6.4 and below + @SuppressWarnings({"unchecked", "rawtypes"}) + private static NbtWrapper createTagWithName(NbtType type, String name) throws Exception { + Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name); + + if (type == NbtType.TAG_COMPOUND) + return (NbtWrapper) new WrappedCompound(handle); + else if (type == NbtType.TAG_LIST) + return (NbtWrapper) new WrappedList(handle); + else + return new WrappedElement(handle); + } + + // For Minecraft 1.7.2 and above + @SuppressWarnings({"unchecked", "rawtypes"}) + private static NbtWrapper createTagSetName(NbtType type, String name) throws Exception { + Object handle = methodCreateTag.invoke(null, (byte) type.getRawID()); + + if (type == NbtType.TAG_COMPOUND) + return (NbtWrapper) new WrappedCompound(handle, name); + else if (type == NbtType.TAG_LIST) + return (NbtWrapper) new WrappedList(handle, name); + else + return new WrappedElement(handle, name); + } /** * Create a new NBT wrapper from a given type. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java index 7ec1214e..e861e41d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedCompound.java @@ -69,6 +69,15 @@ class WrappedCompound implements NbtWrapper>>, Iterable>(handle); } + + /** + * Construct a wrapped compound from a given NMS handle. + * @param handle - the NMS handle. + * @param name - the name of the current compound. + */ + public WrappedCompound(Object handle, String name) { + this.container = new WrappedElement>(handle, name); + } @Override public boolean accept(NbtVisitor visitor) { @@ -128,17 +137,25 @@ class WrappedCompound implements NbtWrapper>>, Iterable>(container.getValue()) { @Override protected Object toInner(NbtBase outer) { - if (outer == null) + if (outer == null) return null; return NbtFactory.fromBase(outer).getHandle(); } + @SuppressWarnings("deprecation") protected NbtBase toOuter(Object inner) { if (inner == null) return null; return NbtFactory.fromNMS(inner); }; + @Override + protected NbtBase toOuter(String key, Object inner) { + if (inner == null) + return null; + return NbtFactory.fromNMS(inner, key); + } + @Override public String toString() { return WrappedCompound.this.toString(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java index 936f4c2b..76146756 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedElement.java @@ -34,15 +34,14 @@ import com.google.common.base.Objects; * @param - type of the value field. */ class WrappedElement implements NbtWrapper { - // Structure modifier for the base class - private static volatile StructureModifier baseModifier; - // For retrieving the current type ID private static volatile Method methodGetTypeID; - // For handling cloning private static volatile Method methodClone; + // Which name property to use + private static volatile Boolean hasNbtName; + // Structure modifiers for the different NBT elements private static StructureModifier[] modifiers = new StructureModifier[NbtType.values().length]; @@ -52,27 +51,43 @@ class WrappedElement implements NbtWrapper { // Saved type private NbtType type; + // Saved name + private NameProperty nameProperty; + /** * Initialize a NBT wrapper for a generic element. * @param handle - the NBT element to wrap. */ public WrappedElement(Object handle) { this.handle = handle; + initializeProperty(); } /** - * Retrieve the modifier (with no target) that is used to read and write the NBT name. - * @return A modifier for accessing the NBT name. + * Initialize a NBT wrapper for a generic element. + * @param handle - the NBT element to wrap. */ - protected static StructureModifier getBaseModifier() { - if (baseModifier == null) { + public WrappedElement(Object handle, String name) { + this.handle = handle; + initializeProperty(); + setName(name); + } + + private void initializeProperty() { + if (nameProperty == null) { Class base = MinecraftReflection.getNBTBaseClass(); - - // This will be the same for all classes, so we'll share modifier - baseModifier = new StructureModifier(base, Object.class, false).withType(String.class); + + // Determine if we have a NBT string field + if (hasNbtName == null) { + hasNbtName = NameProperty.hasStringIndex(base, 0); + } + + // Now initialize the name property + if (hasNbtName) + this.nameProperty = NameProperty.fromStringIndex(base, handle, 0); + else + this.nameProperty = NameProperty.fromBean(); } - - return baseModifier.withType(String.class); } /** @@ -159,12 +174,12 @@ class WrappedElement implements NbtWrapper { @Override public String getName() { - return getBaseModifier().withTarget(handle).read(0); + return nameProperty.getName(); } @Override public void setName(String name) { - getBaseModifier().withTarget(handle).write(0, name); + nameProperty.setName(name); } @Override @@ -194,7 +209,7 @@ class WrappedElement implements NbtWrapper { } try { - return NbtFactory.fromNMS(methodClone.invoke(handle)); + return NbtFactory.fromNMS(methodClone.invoke(handle), getName()); } catch (Exception e) { throw new FieldAccessException("Unable to clone " + handle, e); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java index ad3e0edb..44adc534 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/WrappedList.java @@ -108,6 +108,16 @@ class WrappedList implements NbtWrapper>>, Iterable>(handle); this.elementType = container.getSubType(); } + + /** + * Construct a list from an NMS instance. + * @param handle - NMS instance. + * @param name - name of the current list. + */ + public WrappedList(Object handle, String name) { + this.container = new WrappedElement>(handle, name); + this.elementType = container.getSubType(); + } @Override public boolean accept(NbtVisitor visitor) { @@ -209,7 +219,7 @@ class WrappedList implements NbtWrapper>>, Iterable toOuter(Object inner) { if (inner == null) return null; - return NbtFactory.fromNMS(inner); + return NbtFactory.fromNMS(inner, null); } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java index 33812c6a..9f2c477a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/nbt/io/NbtBinarySerializer.java @@ -59,7 +59,7 @@ public class NbtBinarySerializer { } try { - return NbtFactory.fromNMS(methodLoad.invoke(null, source)); + return NbtFactory.fromNMS(methodLoad.invoke(null, source), null); } catch (Exception e) { throw new FieldAccessException("Unable to read NBT from " + source, e); } diff --git a/ProtocolLib/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java b/ProtocolLib/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java index 1a3365c1..492eaece 100644 --- a/ProtocolLib/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java +++ b/ProtocolLib/src/test/java/com/comphenix/integration/protocol/SimpleCraftBukkitITCase.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; -import org.apache.commons.io.FileUtils; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; @@ -26,6 +25,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.reflect.FieldUtils; import com.google.common.collect.Lists; +import com.google.common.io.Files; // Damn final classes ... @RunWith(org.powermock.modules.junit4.PowerMockRunner.class) @@ -91,13 +91,14 @@ public class SimpleCraftBukkitITCase { * Copy ProtocolLib into the plugins folder. * @throws IOException If anything went wrong. */ + @SuppressWarnings("deprecation") private static void setupPlugins() throws IOException { File pluginDirectory = new File("plugins/"); File bestFile = null; int bestLength = Integer.MAX_VALUE; // Copy the ProtocolLib plugin to the server - FileUtils.cleanDirectory(pluginDirectory); + Files.deleteDirectoryContents(pluginDirectory); for (File file : new File("../").listFiles()) { String name = file.getName(); @@ -107,7 +108,7 @@ public class SimpleCraftBukkitITCase { bestFile = file; } } - FileUtils.copyFile(bestFile, new File(pluginDirectory, bestFile.getName())); + Files.copy(bestFile, new File(pluginDirectory, bestFile.getName())); } /** diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java b/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java index 5eb44bc5..3ac4b374 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java @@ -4,10 +4,13 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import net.minecraft.server.v1_6_R3.StatisticList; +import net.minecraft.server.v1_7_R1.Block; +import net.minecraft.server.v1_7_R1.Item; +import net.minecraft.server.v1_7_R1.RegistryMaterials; +import net.minecraft.server.v1_7_R1.StatisticList; // Will have to be updated for every version though -import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -37,9 +40,12 @@ public class BukkitInitialization { initializePackage(); try { - StatisticList.b(); + Block.p(); + Item.l(); + StatisticList.a(); } catch (Exception e) { // Swallow + e.printStackTrace(); } // Mock the server object @@ -48,6 +54,7 @@ public class BukkitInitialization { ItemMeta mockedMeta = mock(ItemMeta.class); when(mockedServer.getItemFactory()).thenReturn(mockedFactory); + when(mockedServer.isPrimaryThread()).thenReturn(true); when(mockedFactory.getItemMeta(any(Material.class))).thenReturn(mockedMeta); // Inject this fake server @@ -63,6 +70,6 @@ public class BukkitInitialization { */ public static void initializePackage() { // Initialize reflection - MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_6_R3", "org.bukkit.craftbukkit.v1_6_R3"); + MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_7_R1", "org.bukkit.craftbukkit.v1_7_R1"); } } diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 8a64f957..3a8125e0 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -22,15 +22,15 @@ import java.lang.reflect.Array; import java.util.List; import java.util.UUID; -import net.minecraft.server.v1_6_R3.AttributeModifier; -import net.minecraft.server.v1_6_R3.AttributeSnapshot; -import net.minecraft.server.v1_6_R3.Packet44UpdateAttributes; +import net.minecraft.server.v1_7_R1.AttributeModifier; +import net.minecraft.server.v1_7_R1.AttributeSnapshot; +import net.minecraft.server.v1_7_R1.PacketPlayOutUpdateAttributes; import org.apache.commons.lang.SerializationUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; // Will have to be updated for every version though -import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory; import org.bukkit.Material; import org.bukkit.WorldType; @@ -43,6 +43,7 @@ import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; import com.comphenix.protocol.Packets; import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.reflect.EquivalentConverter; @@ -120,7 +121,7 @@ public class PacketContainerTest { @Test public void testGetShorts() { - PacketContainer itemData = new PacketContainer(Packets.Server.ITEM_DATA); + PacketContainer itemData = new PacketContainer(Packets.Server.TRANSACTION); testPrimitive(itemData.getShorts(), 0, (short)0, (short)1); } @@ -150,7 +151,7 @@ public class PacketContainerTest { @Test public void testGetStrings() { - PacketContainer explosion = new PacketContainer(Packets.Server.CHAT); + PacketContainer explosion = new PacketContainer(PacketType.Play.Client.CHAT); testPrimitive(explosion.getStrings(), 0, null, "hello"); } @@ -183,11 +184,12 @@ public class PacketContainerTest { StructureModifier items = windowClick.getItemModifier(); ItemStack goldAxe = new ItemStack(Material.GOLD_AXE); + assertNotNull(goldAxe.getType()); assertNull(items.read(0)); // Insert the goldaxe and check if it's there items.write(0, goldAxe); - assertTrue(equivalentItem(goldAxe, items.read(0))); + assertTrue("Item " + goldAxe + " != " + items.read(0), equivalentItem(goldAxe, items.read(0))); } @Test @@ -292,10 +294,12 @@ public class PacketContainerTest { List positions = Lists.newArrayList(); positions.add(new ChunkPosition(1, 2, 3)); positions.add(new ChunkPosition(3, 4, 5)); - + // Insert and read back positionAccessor.write(0, positions); - assertEquals(positions, positionAccessor.read(0)); + List cloned = positionAccessor.read(0); + + assertEquals(positions, cloned); } @Test @@ -319,7 +323,7 @@ public class PacketContainerTest { @Test public void testSerialization() { - PacketContainer chat = new PacketContainer(3); + PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT); chat.getStrings().write(0, "Test"); PacketContainer copy = (PacketContainer) SerializationUtils.clone(chat); @@ -337,7 +341,7 @@ public class PacketContainerTest { List modifiers = Lists.newArrayList( new AttributeModifier(UUID.randomUUID(), "Unknown synced attribute modifier", 10, 0)); AttributeSnapshot snapshot = new AttributeSnapshot( - (Packet44UpdateAttributes) attribute.getHandle(), "generic.Maxhealth", 20.0, modifiers); + (PacketPlayOutUpdateAttributes) attribute.getHandle(), "generic.Maxhealth", 20.0, modifiers); attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot)); PacketContainer cloned = attribute.deepClone(); @@ -367,25 +371,17 @@ public class PacketContainerTest { @Test public void testDeepClone() { // Try constructing all the packets - for (Integer id : Iterables.concat( - Packets.getClientRegistry().values(), - Packets.getServerRegistry().values() )) { - + for (PacketType type : PacketType.values()) { // Whether or not this packet has been registered - boolean registered = Packets.Server.isSupported(id) || - Packets.Client.isSupported(id); + boolean registered = type.isSupported(); try { - PacketContainer constructed = new PacketContainer(id); + PacketContainer constructed = new PacketContainer(type); if (!registered) { - fail("Expected IllegalArgumentException(Packet " + id + " not registered"); + fail("Expected IllegalArgumentException(Packet " + type + " not registered"); } - // Make sure these packets contains fields as well - assertTrue("Constructed packet with no known fields (" + id + ")", - constructed.getModifier().size() > 0); - // Initialize default values constructed.getModifier().writeDefaults(); @@ -396,18 +392,23 @@ public class PacketContainerTest { StructureModifier firstMod = constructed.getModifier(), secondMod = cloned.getModifier(); assertEquals(firstMod.size(), secondMod.size()); - // Make sure all the fields are equivalent - for (int i = 0; i < firstMod.size(); i++) { - if (firstMod.getField(i).getType().isArray()) - assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i))); - else - testEquality(firstMod.read(i), secondMod.read(i)); + if (PacketType.Status.Server.KICK_DISCONNECT.equals(type)) { + assertArrayEquals(SerializationUtils.serialize(constructed), SerializationUtils.serialize(cloned)); + + } else { + // Make sure all the fields are equivalent + for (int i = 0; i < firstMod.size(); i++) { + if (firstMod.getField(i).getType().isArray()) + assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i))); + else + testEquality(firstMod.read(i), secondMod.read(i)); + } } } catch (IllegalArgumentException e) { if (!registered) { // Check the same - assertEquals(e.getMessage(), "The packet ID " + id + " is not registered."); + assertEquals(e.getMessage(), "The packet ID " + type + " is not registered."); } else { // Something is very wrong throw e; diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java index f96be208..3ce104da 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java @@ -8,10 +8,10 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import net.minecraft.server.v1_6_R3.IntHashMap; +import net.minecraft.server.v1_7_R1.IntHashMap; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory; import org.bukkit.inventory.ItemStack; import org.junit.BeforeClass; import org.junit.Test; diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java index a5feaf3b..478e995c 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/WrappedAttributeTest.java @@ -4,9 +4,9 @@ import static org.junit.Assert.*; import java.util.List; -import net.minecraft.server.v1_6_R3.AttributeModifier; -import net.minecraft.server.v1_6_R3.AttributeSnapshot; -import net.minecraft.server.v1_6_R3.Packet44UpdateAttributes; +import net.minecraft.server.v1_7_R1.AttributeModifier; +import net.minecraft.server.v1_7_R1.AttributeSnapshot; +import net.minecraft.server.v1_7_R1.PacketPlayOutUpdateAttributes; import org.junit.Before; import org.junit.BeforeClass; @@ -83,7 +83,7 @@ public class WrappedAttributeTest { modifiers.add((AttributeModifier) wrapper.getHandle()); } return new AttributeSnapshot( - (Packet44UpdateAttributes) attribute.getParentPacket().getHandle(), + (PacketPlayOutUpdateAttributes) attribute.getParentPacket().getHandle(), attribute.getAttributeKey(), attribute.getBaseValue(), modifiers); } diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java index 9d2e1a22..a4236afc 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/wrappers/nbt/NbtFactoryTest.java @@ -27,7 +27,7 @@ import java.io.DataOutput; import java.io.DataOutputStream; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory; import org.bukkit.inventory.ItemStack; import org.junit.BeforeClass; import org.junit.Test;