> 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 extends PacketType> 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 extends ChannelFuture> collection) {
+ List extends ChannelFuture> 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 extends Map>> 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 extends Object> 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.
+ *