Updating ProtocolLib for 1.7.2.

Still a work in progress.
This commit is contained in:
Kristian S. Stangeland 2013-12-04 04:17:02 +01:00
parent 28b4874c60
commit c137640fee
54 changed files with 3981 additions and 584 deletions

View File

@ -231,7 +231,7 @@
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>craftbukkit</artifactId>
<version>1.6.4-R2.0</version>
<version>1.7.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>

View File

@ -70,6 +70,7 @@ class CleanupStaticMembers {
*/
public void resetAll() {
// This list must always be updated
@SuppressWarnings("deprecation")
Class<?>[] publicClasses = {
AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class,
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,

View File

@ -1,8 +1,23 @@
package com.comphenix.protocol;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.bukkit.Bukkit;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.ObjectEnum;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
/**
* Represents the type of a packet in a specific protocol.
@ -15,23 +30,66 @@ public class PacketType implements Serializable {
// Increment whenever the type changes
private static final long serialVersionUID = 1L;
/**
* Represents an unknown legacy packet ID.
*/
public static final int UNKNOWN_PACKET = -1;
/**
* Packets sent during handshake.
* @author Kristian
*/
public static class Handshake {
public static final Protocol PROTOCOL = Protocol.HANDSHAKE;
private static final Protocol PROTOCOL = Protocol.HANDSHAKING;
public static class Client extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.CLIENT;
public final static Client INSTANCE = new Client();
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType HANDSHAKE = new PacketType(PROTOCOL, SENDER, 0x00, 2);
private final static Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(PacketType.class); }
public static Client getInstance() {
return INSTANCE;
}
public static Sender getSender() {
return SENDER;
}
}
/**
* An empty enum, as the server will not send any packets in this protocol.
* @author Kristian
*/
public static class Server extends ObjectEnum<PacketType> {
private final static Sender SENDER = Sender.CLIENT;
private final static Server INSTANCE = new Server();
private Server() { super(PacketType.class); }
public static Server getInstance() {
return INSTANCE;
}
public static Sender getSender() {
return SENDER;
}
}
public static Protocol getProtocol() {
return PROTOCOL;
}
}
public static class Game {
public static final Protocol PROTOCOL = Protocol.GAME;
/**
* Packets sent and recieved when logged into the game.
* @author Kristian
*/
public static class Play {
private static final Protocol PROTOCOL = Protocol.GAME;
public static class Server extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.SERVER;
public final static Server INSTANCE = new Server();
public static class Server extends ObjectEnum<PacketType> {
private final static Sender SENDER = Sender.SERVER;
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x00, 0);
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x01, 1);
@ -83,7 +141,7 @@ public class PacketType implements Serializable {
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, 103);
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x30, 104);
public static final PacketType CRAFT_PROGRESS_BAR = new PacketType(PROTOCOL, SENDER, 0x31, 105);
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x32, 106);
public static final PacketType TRANSACTION = new PacketType(PROTOCOL, SENDER, 0x32, 106);
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x33, 130);
public static final PacketType ITEM_DATA = new PacketType(PROTOCOL, SENDER, 0x34, 131);
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x35, 132);
@ -99,11 +157,23 @@ public class PacketType implements Serializable {
public static final PacketType SET_SCOREOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x3E, 209);
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x3F, 250);
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x40, 255);
// The instance must
private final static Server INSTANCE = new Server();
// Prevent accidental construction
private Server() { super(PacketType.class); }
public static Sender getSender() {
return SENDER;
}
public static Server getInstance() {
return INSTANCE;
}
}
public static class Client extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.CLIENT;
public final static Client INSTANCE = new Client();
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x00, 0);
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x01, 3);
@ -129,51 +199,127 @@ public class PacketType implements Serializable {
public static final PacketType LOCALE_AND_VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x15, 204);
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x16, 205);
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x17, 250);
private final static Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(PacketType.class); }
public static Sender getSender() {
return SENDER;
}
public static Client getInstance() {
return INSTANCE;
}
}
public static Protocol getProtocol() {
return PROTOCOL;
}
}
/**
* Packets sent and recieved when querying the server in the multiplayer menu.
* @author Kristian
*/
public static class Status {
public static final Protocol PROTOCOL = Protocol.STATUS;
private static final Protocol PROTOCOL = Protocol.STATUS;
public static class Server extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.SERVER;
public final static Server INSTANCE = new Server();
private final static Sender SENDER = Sender.SERVER;
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, 255);
@SuppressWarnings("deprecation")
public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Server.PING_TIME);
public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x01, Packets.Server.PING_TIME);
private final static Server INSTANCE = new Server();
// Prevent accidental construction
private Server() { super(PacketType.class); }
public static Sender getSender() {
return SENDER;
}
public static Server getInstance() {
return INSTANCE;
}
}
public static class Client extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.CLIENT;
public final static Client INSTANCE = new Client();
private final static Sender SENDER = Sender.CLIENT;
public static final PacketType STATUS_REQUEST = new PacketType(PROTOCOL, SENDER, 0x00, 254);
@SuppressWarnings("deprecation")
public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.PING_TIME);
public static final PacketType PING_TIME = new PacketType(PROTOCOL, SENDER, 0x01, Packets.Client.PING_TIME);
private final static Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(PacketType.class); }
public static Sender getSender() {
return SENDER;
}
public static Client getInstance() {
return INSTANCE;
}
}
public static Protocol getProtocol() {
return PROTOCOL;
}
}
/**
* Packets sent and recieved when logging in to the server.
* @author Kristian
*/
public static class Login {
public static final Protocol PROTOCOL = Protocol.LOGIN;
private static final Protocol PROTOCOL = Protocol.LOGIN;
public static class Server extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.SERVER;
public final static Server INSTANCE = new Server();
private final static Sender SENDER = Sender.SERVER;
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, 255);
public static final PacketType KEY_REQUEST = new PacketType(PROTOCOL, SENDER, 0x01, 253);
@SuppressWarnings("deprecation")
public static final PacketType LOGIN_SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, Packets.Server.LOGIN_SUCCESS);
private final static Server INSTANCE = new Server();
// Prevent accidental construction
private Server() { super(PacketType.class); }
public static Sender getSender() {
return SENDER;
}
public static Server getInstance() {
return INSTANCE;
}
}
public static class Client extends ObjectEnum<PacketType> {
public final static Sender SENDER = Sender.CLIENT;
public final static Client INSTANCE = new Client();
private final static Sender SENDER = Sender.CLIENT;
@SuppressWarnings("deprecation")
public static final PacketType LOGIN_START = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.LOGIN_START);
public static final PacketType LOGIN_START = new PacketType(PROTOCOL, SENDER, 0x00, Packets.Client.LOGIN_START);
public static final PacketType KEY_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x01, 252);
private final static Client INSTANCE = new Client();
// Prevent accidental construction
private Client() { super(PacketType.class); }
public static Sender getSender() {
return SENDER;
}
public static Client getInstance() {
return INSTANCE;
}
}
public static Protocol getProtocol() {
return PROTOCOL;
}
}
@ -182,10 +328,29 @@ public class PacketType implements Serializable {
* @author Kristian
*/
public enum Protocol {
HANDSHAKE,
HANDSHAKING,
GAME,
STATUS,
LOGIN
LOGIN;
/**
* Retrieve the correct protocol enum from a given vanilla enum instance.
* @param vanilla - the vanilla protocol enum instance.
* @return The corresponding protocol.
*/
public static Protocol fromVanilla(Enum<?> vanilla) {
String name = vanilla.name();
if ("HANDSHAKING".equals(name))
return HANDSHAKING;
if ("PLAY".equals(name))
return GAME;
if ("STATUS".equals(name))
return STATUS;
if ("LOGIN".equals(name))
return LOGIN;
throw new IllegalArgumentException("Unrecognized vanilla enum " + vanilla);
}
}
/**
@ -205,23 +370,189 @@ public class PacketType implements Serializable {
SERVER
}
// Lookup of packet types
private static PacketTypeLookup LOOKUP;
/**
* Protocol version of all the current IDs.
*/
private static final MinecraftVersion PROTOCOL_VERSION = MinecraftVersion.WORLD_UPDATE;
private final Protocol protocol;
private final Sender sender;
private final int currentId;
private final int legacyId;
private final MinecraftVersion version;
/**
* Retrieve the current packet/legacy lookup.
* @return The packet type lookup.
*/
private static PacketTypeLookup getLookup() {
if (LOOKUP == null) {
LOOKUP = new PacketTypeLookup().
addPacketTypes(Handshake.Client.getInstance()).
addPacketTypes(Handshake.Server.getInstance()).
addPacketTypes(Play.Client.getInstance()).
addPacketTypes(Play.Server.getInstance()).
addPacketTypes(Status.Client.getInstance()).
addPacketTypes(Status.Server.getInstance()).
addPacketTypes(Login.Client.getInstance()).
addPacketTypes(Login.Server.getInstance());
}
return LOOKUP;
}
/**
* Find every packet type known to the current version of ProtocolLib.
* @return Every packet type.
*/
public static Iterable<PacketType> values() {
List<Iterable<? extends PacketType>> 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.
* <p>
* 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.
* <p>
* 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<Boolean> scheduleRegister(final PacketType type, final String name) {
Callable<Boolean> callable = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
ObjectEnum<PacketType> 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.
* <p>
@ -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.
* <p>
* 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 + "]";
}
}

View File

@ -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<PacketType> HANDSHAKE_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> HANDSHAKE_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> GAME_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> STATUS_SERVER = IntegerMap.newMap();
public final IntegerMap<PacketType> LOGIN_CLIENT = IntegerMap.newMap();
public final IntegerMap<PacketType> 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<PacketType> 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<PacketType> legacyLookup = new IntegerMap<PacketType>();
// 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);
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@ -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.
* <p>
* Integers must be non-negative.
* @author Kristian
*/
public class IntegerMap<T> {
private T[] array;
private int size;
/**
* Construct a new integer map.
* @return A new integer map.
*/
public static <T> IntegerMap<T> newMap() {
return new IntegerMap<T>();
}
/**
* 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<Integer, Object> toMap() {
Map<Integer, Object> map = Maps.newHashMap();
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
map.put(i, array[i]);
}
}
return map;
}
}

View File

@ -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.
* <p>

View File

@ -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<PacketType> 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<Object> 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<Object> 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.
* <p>
* 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.

View File

@ -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.
* <p>
* 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.");
}

View File

@ -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.
*/

View File

@ -44,6 +44,7 @@ public interface ListenerInvoker {
* @param packet - the packet.
* @return The packet ID.
*/
@Deprecated
public abstract int getPacketID(Object packet);
/**

View File

@ -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);
}
}

View File

@ -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.
* <p>
@ -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<Unwrapper> unwrappers;
@ -62,8 +62,8 @@ public class PacketConstructor {
this.unwrappers.addAll(BukkitConverters.getUnwrappers());
}
private PacketConstructor(int packetID, Constructor<?> constructorMethod, List<Unwrapper> unwrappers, Unwrapper[] paramUnwrapper) {
this.packetID = packetID;
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> 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.
* <p>
* 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<Unwrapper> 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.
* <p>
* Note that if you pass a Class<?> as a value, it will use its type directly.
* @param id - packet ID.
* <p>
* 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.
* <p>
* 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;

View File

@ -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();

View File

@ -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<Integer, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
private static ConcurrentMap<PacketType, StructureModifier<Object>> structureModifiers =
new ConcurrentHashMap<PacketType, StructureModifier<Object>>();
private static Set<Integer> compiling = new HashSet<Integer>();
private static Set<PacketType> compiling = new HashSet<PacketType>();
/**
* Creates an empty Minecraft packet of the given ID.
* @param id - packet ID.
* Creates an empty Minecraft packet of the given id.
* <p>
* 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.
* <p>
* Deprecated: Use {@link #getStructure(PacketType)} instead.
* @param legacyId - the legacy (1.6.4) packet ID.
* @return A structure modifier.
*/
public static StructureModifier<Object> getStructure(int id) {
@Deprecated
public static StructureModifier<Object> 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<Object> 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<Object> 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.
* <p>
* 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<Object> getStructure(int id, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(id);
@Deprecated
public static StructureModifier<Object> 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<Object> getStructure(final PacketType type, boolean compile) {
StructureModifier<Object> result = structureModifiers.get(type);
// We don't want to create this for every lookup
if (result == null) {
// Use the vanilla class definition
final StructureModifier<Object> value = new StructureModifier<Object>(
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<Object>() {
@Override
public void onCompiled(StructureModifier<Object> compiledModifier) {
structureModifiers.put(idCopy, compiledModifier);
structureModifiers.put(type, compiledModifier);
}
});
compiling.add(id);
compiling.add(type);
}
}
}
return result;
}
}

View File

@ -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<ChannelFuture> {
private List<ChannelFuture> delegate;
private ChannelHandler handler;
/**
* Construct a new bootstrap list.
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public BootstrapList(List<ChannelFuture> delegate, ChannelHandler handler) {
this.delegate = delegate;
this.handler = handler;
// Process all existing bootstraps
for (ChannelFuture future : this)
processBootstrap(future);
}
@Override
protected List<ChannelFuture> 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);
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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<Player, ChannelInjector> 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<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
private ConcurrentMap<NetworkMarker, PacketEvent> markerEvent = new MapMaker().weakKeys().makeMap();
// Packets to ignore
private Set<Object> ignoredPackets = Collections.newSetFromMap(new MapMaker().weakKeys().<Object, Boolean>makeMap());
// Other handlers
private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder;
private MethodAccessor decodeBuffer;
private MethodAccessor encodeBuffer;
// Our extra handler
private MessageToByteEncoder<Object> 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<Object>) 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<Object>() {
@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<Object> 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<String, ChannelHandler> 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;
}
}
}

View File

@ -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<Method> 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<Method> 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);
}

View File

@ -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()) {
}
}
}

View File

@ -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<Integer> 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<Channel>() {
@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<ChannelFuture>) 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<Object> list = (List<Object>) 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<Object> bootstraps = (List<Object>) 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<Integer> set) {
bufferedPackets = set;
}
};
}
}

View File

@ -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<PacketType, Class<?>> typeToClass = HashBiMap.create();
private Set<PacketType> serverPackets = Sets.newHashSet();
private Set<PacketType> 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<PacketType, Class<?>> getPacketTypeLookup() {
return Collections.unmodifiableMap(typeToClass);
}
/**
* Retrieve an immutable view of the class to packet tyåe lookup.
* @return The packet type lookup.
*/
public Map<Class<?>, PacketType> getPacketClassLookup() {
return Collections.unmodifiableMap(typeToClass.inverse());
}
/**
* Retrieve every known client packet, from every protocol.
* @return Every client packet.
*/
public Set<PacketType> getClientPackets() {
return Collections.unmodifiableSet(clientPackets);
}
/**
* Retrieve every known server packet, from every protocol.
* @return Every server packet.
*/
public Set<PacketType> getServerPackets() {
return Collections.unmodifiableSet(serverPackets);
}
/**
* Load the packet lookup tables in each protocol.
*/
private void initialize() {
final Object[] protocols = enumProtocol.getEnumConstants();
List<Map<Integer, Class<?>>> serverPackets = Lists.newArrayList();
List<Map<Integer, Class<?>>> clientPackets = Lists.newArrayList();
StructureModifier<Object> modifier = null;
for (Object protocol : protocols) {
if (modifier == null)
modifier = new StructureModifier<Object>(protocol.getClass().getSuperclass(), false);
StructureModifier<Map<Integer, Class<?>>> 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<Map<Integer, Class<?>>> 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<Integer, Class<?>> lookup, Protocol protocol, Sender sender) {
for (Entry<Integer, Class<?>> 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<Integer, Class<?>>> maps) {
int count = 0;
for (Map<Integer, Class<?>> map : maps)
count += map.size();
return count;
}
}

View File

@ -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();
}
}

View File

@ -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<Class, Integer> packetToID;
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
private Multimap<Integer, Class> customIdToPacket;
private Map<Integer, Class> vanillaIdToPacket;
// Whether or not certain packets are sent by the client or the server
private ImmutableSet<Integer> serverPackets;
private ImmutableSet<Integer> clientPackets;
// The underlying sets
private Set<Integer> serverPacketsRef;
private Set<Integer> clientPacketsRef;
// New proxy values
private Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
/**
* Initialize the registry.
*/
@SuppressWarnings({ "unchecked" })
public void initialize() {
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) 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<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> 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<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> entry) {
return MinecraftReflection.isMinecraftClass(entry.getKey());
}
});
}
initializeSets();
}
@SuppressWarnings("unchecked")
private void initializeSets() throws FieldAccessException {
if (serverPacketsRef == null || clientPacketsRef == null) {
List<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) 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<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
initialize();
}
return packetToID;
}
private Map<Class, Integer> 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<Integer, Class> getOverwrittenPackets() {
return overwrittenPackets;
}
/**
* Retrieve the vanilla classes handling each packet ID.
* @return Vanilla classes.
*/
public Map<Integer, Class> 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<Integer> 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<Integer> 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<Integer, Class> 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);
}
}
}

View File

@ -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
*/

View File

@ -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<Class, Integer> packetToID;
// Cached for legacy
private static volatile Set<PacketType> NETTY_SERVER_PACKETS;
private static volatile Set<PacketType> NETTY_CLIENT_PACKETS;
// Packet IDs to classes, grouped by whether or not they're vanilla or custom defined
private static Multimap<Integer, Class> customIdToPacket;
private static Map<Integer, Class> vanillaIdToPacket;
// Cached for Netty
private static volatile Set<Integer> LEGACY_SERVER_PACKETS;
private static volatile Set<Integer> LEGACY_CLIENT_PACKETS;
private static volatile Map<Integer, Class> LEGACY_PREVIOUS_PACKETS;
// Whether or not certain packets are sent by the client or the server
private static ImmutableSet<Integer> serverPackets;
private static ImmutableSet<Integer> clientPackets;
// Whether or not the registry has
private static boolean INITIALIZED;
// The underlying sets
private static Set<Integer> serverPacketsRef;
private static Set<Integer> clientPacketsRef;
// New proxy values
private static Map<Integer, Class> overwrittenPackets = new HashMap<Integer, Class>();
// Vanilla packets
private static Map<Integer, Class> previousValues = new HashMap<Integer, Class>();
@SuppressWarnings({ "unchecked" })
public static Map<Class, Integer> getPacketToID() {
// Initialize it, if we haven't already
if (packetToID == null) {
try {
Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class);
packetToID = (Map<Class, Integer>) 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<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> 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<Map.Entry<Class, Integer>>() {
@Override
public boolean apply(@Nullable Entry<Class, Integer> 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<Class, Integer> 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.
* <p>
* Deprecated: Use {@link #getPacketToType()} instead.
* @return A map of packet classes and their corresponding ID.
*/
@Deprecated
public static Map<Class, Integer> getPacketToID() {
initialize();
if (NETTY != null) {
@SuppressWarnings("unchecked")
Map<Class, Integer> result = (Map)Maps.transformValues(NETTY.getPacketClassLookup(), new Function<PacketType, Integer>() {
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<Class, PacketType> getPacketToType() {
initialize();
if (NETTY != null) {
@SuppressWarnings("unchecked")
Map<Class, PacketType> result = (Map)NETTY.getPacketClassLookup();
return result;
}
return Maps.transformValues(LEGACY.getPacketToID(), new Function<Integer, PacketType>() {
public PacketType apply(Integer packetId) {
return PacketType.findLegacy(packetId);
};
});
}
/**
* Retrieve the injected proxy classes handlig each packet ID.
* <p>
* This is not supported in 1.7.2 and later.
* @return Injected classes.
*/
@Deprecated
public static Map<Integer, Class> 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<Integer, Class> getPreviousPackets() {
return previousValues;
initialize();
if (NETTY != null) {
// Construct it first
if (LEGACY_PREVIOUS_PACKETS == null) {
Map<Integer, Class> map = Maps.newHashMap();
for (Entry<PacketType, Class<?>> 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.
* <p>
* 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<Integer> 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<PacketType> 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.
* <p>
* 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<Integer> 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<Field> sets = getPacketRegistry().getFieldListByType(Set.class);
try {
if (sets.size() > 1) {
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
clientPacketsRef = (Set<Integer>) 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<PacketType> 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<Integer> toLegacy(Set<PacketType> types) {
Set<Integer> 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<PacketType> toPacketTypes(Set<Integer> ids) {
Set<PacketType> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Integer, Class> 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.
* <p>
* 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));
}
}

View File

@ -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<Integer> getPacketHandlers() {
return reveivedFilters.toSet();
}
@Override
public void cleanupAll() {
reveivedFilters.clear();
}
}

View File

@ -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<Integer> 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<PacketListener> listeners) {
// Yes, really
}
}

View File

@ -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<Integer> set) {
Set<Integer> 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<Integer> 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();
}
}

View File

@ -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<Integer> 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<PacketListener> listeners) {
// Yes, really
}
@Override
public void updatePlayer(Player player) {
// Do nothing

View File

@ -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> T getFieldValue(Object instance, Class<T> 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<Field> getDeclaredFields(Class<?> excludeClass) {
if (forceAccess) {
Class<?> current = source;
Set<Field> 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).
* <p>
@ -509,7 +677,6 @@ public class FuzzyReflection {
result.add(element);
}
}
return result;
}

View File

@ -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<T> {
public class ObjectEnum<T> implements Iterable<T> {
// Used to convert between IDs and names
protected BiMap<T, String> members = HashBiMap.create();
/**
* Registers every declared integer field.
*/
public ObjectEnum() {
registerAll();
public ObjectEnum(Class<T> 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<T> 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<T> {
}
/**
* 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<T> {
public Set<T> values() {
return new HashSet<T>(members.keySet());
}
@Override
public Iterator<T> iterator() {
return members.keySet().iterator();
}
}

View File

@ -581,7 +581,7 @@ public class StructureModifier<TField> {
List<Field> result = new ArrayList<Field>();
// 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<TField> {
return result;
}
}

View File

@ -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.");
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Method> 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.");
}
}
}

View File

@ -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.
* <p>
* 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);
}

View File

@ -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<MinecraftVersion> {
public class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
private static final long serialVersionUID = 1L;
/**
* Regular expression used to parse version strings.
*/

View File

@ -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<SnapshotVersion> {
public class SnapshotVersion implements Comparable<SnapshotVersion>, 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<SnapshotVersion> {
* @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<SnapshotVersion> {
@Override
public String toString() {
return rawString;
return getSnapshotString();
}
}

View File

@ -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<Integer> 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 + "]";
}
}

View File

@ -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<WrappedWatchableObject> {
/**
* Used to assign integer IDs to given types.
*/
@ -61,12 +61,16 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
// 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<WrappedWatchableObject> {
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<WrappedWatchableObject> {
initialize();
}
/**
* Construct a new data watcher with the given entity.
* <p>
* 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.
* <p>
* 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.
* <p>
@ -548,6 +592,11 @@ public class WrappedDataWatcher implements Iterable<WrappedWatchableObject> {
// 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<WrappedWatchableObject> {
public String toString() {
return asMap().toString();
}
/**
* Retrieve the entity associated with this data watcher.
* <p>
* <b>Warning:</b> 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.
* <p>
* <b>Warning:</b> 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);
}
}
}

View File

@ -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<Key, VInner, VOuter> 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<Key, VInner, VOuter> 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<Key, VInner, VOuter> 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<Key, VInner, VOuter> 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<Key, VInner, VOuter> extends AbstractConverte
@Override
public Collection<VOuter> values() {
return new ConvertedCollection<VInner, VOuter>(inner.values()) {
return Collections2.transform(entrySet(), new Function<Entry<Key, VOuter>, VOuter>() {
@Override
protected VOuter toOuter(VInner inner) {
return ConvertedMap.this.toOuter(inner);
public VOuter apply(@Nullable java.util.Map.Entry<Key, VOuter> entry) {
return entry.getValue();
}
@Override
protected VInner toInner(VOuter outer) {
return ConvertedMap.this.toInner(outer);
}
};
});
}
/**

View File

@ -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<Class<?>, StructureModifier<String>> 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<String> getModifier(Class<?> baseClass) {
StructureModifier<String> modifier = MODIFIERS.get(baseClass);
// Share modifier
if (modifier == null) {
modifier = new StructureModifier<Object>(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<String> 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;
}
};
}
}

View File

@ -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<Object> itemStackModifier;
@ -188,10 +189,13 @@ public class NbtFactory {
/**
* Initialize a NBT wrapper.
* <p>
* Use {@link #fromNMS(Object, String)} instead.
* @param handle - the underlying net.minecraft.server object to wrap.
* @return A NBT wrapper.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@Deprecated
public static <T> NbtWrapper<T> fromNMS(Object handle) {
WrappedElement<T> partial = new WrappedElement<T>(handle);
@ -203,6 +207,25 @@ public class NbtFactory {
else
return partial;
}
/**
* Initialize a NBT wrapper with a name.
* @param name - the name of the tag, or NULL if not valid.
* @param handle - the underlying net.minecraft.server object to wrap.
* @return A NBT wrapper.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> NbtWrapper<T> fromNMS(Object handle, String name) {
WrappedElement<T> partial = new WrappedElement<T>(handle, name);
// See if this is actually a compound tag
if (partial.getType() == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) new WrappedCompound(handle, name);
else if (partial.getType() == NbtType.TAG_LIST)
return new WrappedList(handle, name);
else
return partial;
}
/**
* Retrieve the NBT compound from a given NMS handle.
@ -351,7 +374,6 @@ public class NbtFactory {
* @return The new wrapped NBT tag.
* @throws FieldAccessException If we're unable to create the underlying tag.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> NbtWrapper<T> ofWrapper(NbtType type, String name) {
if (type == null)
throw new IllegalArgumentException("type cannot be NULL.");
@ -362,19 +384,22 @@ public class NbtFactory {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// Use the base class
methodCreateTag = FuzzyReflection.fromClass(base).
getMethodByParameters("createTag", base, new Class<?>[] { byte.class, String.class });
try {
methodCreateTag = findCreateMethod(base, byte.class, String.class);
methodCreateWithName = true;
} catch (Exception e) {
methodCreateTag = findCreateMethod(base, byte.class);
methodCreateWithName = false;
}
}
try {
Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name);
if (type == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) new WrappedCompound(handle);
else if (type == NbtType.TAG_LIST)
return (NbtWrapper<T>) new WrappedList(handle);
// Delegate to the correct version
if (methodCreateWithName)
return createTagWithName(type, name);
else
return new WrappedElement<T>(handle);
return createTagSetName(type, name);
} catch (Exception e) {
// Inform the caller
@ -383,6 +408,43 @@ public class NbtFactory {
e);
}
}
/**
* Find the create method of NBTBase.
* @param base - the base NBT.
* @param params - the parameters.
*/
private static Method findCreateMethod(Class<?> base, Class<?>... params) {
Method method = FuzzyReflection.fromClass(base, true).getMethodByParameters("createTag", base, params);
method.setAccessible(true);
return method;
}
// For Minecraft 1.6.4 and below
@SuppressWarnings({"unchecked", "rawtypes"})
private static <T> NbtWrapper<T> createTagWithName(NbtType type, String name) throws Exception {
Object handle = methodCreateTag.invoke(null, (byte) type.getRawID(), name);
if (type == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) new WrappedCompound(handle);
else if (type == NbtType.TAG_LIST)
return (NbtWrapper<T>) new WrappedList(handle);
else
return new WrappedElement<T>(handle);
}
// For Minecraft 1.7.2 and above
@SuppressWarnings({"unchecked", "rawtypes"})
private static <T> NbtWrapper<T> createTagSetName(NbtType type, String name) throws Exception {
Object handle = methodCreateTag.invoke(null, (byte) type.getRawID());
if (type == NbtType.TAG_COMPOUND)
return (NbtWrapper<T>) new WrappedCompound(handle, name);
else if (type == NbtType.TAG_LIST)
return (NbtWrapper<T>) new WrappedList(handle, name);
else
return new WrappedElement<T>(handle, name);
}
/**
* Create a new NBT wrapper from a given type.

View File

@ -69,6 +69,15 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
public WrappedCompound(Object handle) {
this.container = new WrappedElement<Map<String,Object>>(handle);
}
/**
* Construct a wrapped compound from a given NMS handle.
* @param handle - the NMS handle.
* @param name - the name of the current compound.
*/
public WrappedCompound(Object handle, String name) {
this.container = new WrappedElement<Map<String,Object>>(handle, name);
}
@Override
public boolean accept(NbtVisitor visitor) {
@ -128,17 +137,25 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
savedMap = new ConvertedMap<String, Object, NbtBase<?>>(container.getValue()) {
@Override
protected Object toInner(NbtBase<?> outer) {
if (outer == null)
if (outer == null)
return null;
return NbtFactory.fromBase(outer).getHandle();
}
@SuppressWarnings("deprecation")
protected NbtBase<?> toOuter(Object inner) {
if (inner == null)
return null;
return NbtFactory.fromNMS(inner);
};
@Override
protected NbtBase<?> toOuter(String key, Object inner) {
if (inner == null)
return null;
return NbtFactory.fromNMS(inner, key);
}
@Override
public String toString() {
return WrappedCompound.this.toString();

View File

@ -34,15 +34,14 @@ import com.google.common.base.Objects;
* @param <TType> - type of the value field.
*/
class WrappedElement<TType> implements NbtWrapper<TType> {
// Structure modifier for the base class
private static volatile StructureModifier<Object> baseModifier;
// For retrieving the current type ID
private static volatile Method methodGetTypeID;
// For handling cloning
private static volatile Method methodClone;
// Which name property to use
private static volatile Boolean hasNbtName;
// Structure modifiers for the different NBT elements
private static StructureModifier<?>[] modifiers = new StructureModifier<?>[NbtType.values().length];
@ -52,27 +51,43 @@ class WrappedElement<TType> implements NbtWrapper<TType> {
// Saved type
private NbtType type;
// Saved name
private NameProperty nameProperty;
/**
* Initialize a NBT wrapper for a generic element.
* @param handle - the NBT element to wrap.
*/
public WrappedElement(Object handle) {
this.handle = handle;
initializeProperty();
}
/**
* Retrieve the modifier (with no target) that is used to read and write the NBT name.
* @return A modifier for accessing the NBT name.
* Initialize a NBT wrapper for a generic element.
* @param handle - the NBT element to wrap.
*/
protected static StructureModifier<String> getBaseModifier() {
if (baseModifier == null) {
public WrappedElement(Object handle, String name) {
this.handle = handle;
initializeProperty();
setName(name);
}
private void initializeProperty() {
if (nameProperty == null) {
Class<?> base = MinecraftReflection.getNBTBaseClass();
// This will be the same for all classes, so we'll share modifier
baseModifier = new StructureModifier<Object>(base, Object.class, false).withType(String.class);
// Determine if we have a NBT string field
if (hasNbtName == null) {
hasNbtName = NameProperty.hasStringIndex(base, 0);
}
// Now initialize the name property
if (hasNbtName)
this.nameProperty = NameProperty.fromStringIndex(base, handle, 0);
else
this.nameProperty = NameProperty.fromBean();
}
return baseModifier.withType(String.class);
}
/**
@ -159,12 +174,12 @@ class WrappedElement<TType> implements NbtWrapper<TType> {
@Override
public String getName() {
return getBaseModifier().withTarget(handle).read(0);
return nameProperty.getName();
}
@Override
public void setName(String name) {
getBaseModifier().withTarget(handle).write(0, name);
nameProperty.setName(name);
}
@Override
@ -194,7 +209,7 @@ class WrappedElement<TType> implements NbtWrapper<TType> {
}
try {
return NbtFactory.fromNMS(methodClone.invoke(handle));
return NbtFactory.fromNMS(methodClone.invoke(handle), getName());
} catch (Exception e) {
throw new FieldAccessException("Unable to clone " + handle, e);
}

View File

@ -108,6 +108,16 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
this.container = new WrappedElement<List<Object>>(handle);
this.elementType = container.getSubType();
}
/**
* Construct a list from an NMS instance.
* @param handle - NMS instance.
* @param name - name of the current list.
*/
public WrappedList(Object handle, String name) {
this.container = new WrappedElement<List<Object>>(handle, name);
this.elementType = container.getSubType();
}
@Override
public boolean accept(NbtVisitor visitor) {
@ -209,7 +219,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
protected NbtBase<TType> toOuter(Object inner) {
if (inner == null)
return null;
return NbtFactory.fromNMS(inner);
return NbtFactory.fromNMS(inner, null);
}
@Override

View File

@ -59,7 +59,7 @@ public class NbtBinarySerializer {
}
try {
return NbtFactory.fromNMS(methodLoad.invoke(null, source));
return NbtFactory.fromNMS(methodLoad.invoke(null, source), null);
} catch (Exception e) {
throw new FieldAccessException("Unable to read NBT from " + source, e);
}

View File

@ -11,7 +11,6 @@ import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
@ -26,6 +25,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.reflect.FieldUtils;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
// Damn final classes ...
@RunWith(org.powermock.modules.junit4.PowerMockRunner.class)
@ -91,13 +91,14 @@ public class SimpleCraftBukkitITCase {
* Copy ProtocolLib into the plugins folder.
* @throws IOException If anything went wrong.
*/
@SuppressWarnings("deprecation")
private static void setupPlugins() throws IOException {
File pluginDirectory = new File("plugins/");
File bestFile = null;
int bestLength = Integer.MAX_VALUE;
// Copy the ProtocolLib plugin to the server
FileUtils.cleanDirectory(pluginDirectory);
Files.deleteDirectoryContents(pluginDirectory);
for (File file : new File("../").listFiles()) {
String name = file.getName();
@ -107,7 +108,7 @@ public class SimpleCraftBukkitITCase {
bestFile = file;
}
}
FileUtils.copyFile(bestFile, new File(pluginDirectory, bestFile.getName()));
Files.copy(bestFile, new File(pluginDirectory, bestFile.getName()));
}
/**

View File

@ -4,10 +4,13 @@ import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import net.minecraft.server.v1_6_R3.StatisticList;
import net.minecraft.server.v1_7_R1.Block;
import net.minecraft.server.v1_7_R1.Item;
import net.minecraft.server.v1_7_R1.RegistryMaterials;
import net.minecraft.server.v1_7_R1.StatisticList;
// Will have to be updated for every version though
import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@ -37,9 +40,12 @@ public class BukkitInitialization {
initializePackage();
try {
StatisticList.b();
Block.p();
Item.l();
StatisticList.a();
} catch (Exception e) {
// Swallow
e.printStackTrace();
}
// Mock the server object
@ -48,6 +54,7 @@ public class BukkitInitialization {
ItemMeta mockedMeta = mock(ItemMeta.class);
when(mockedServer.getItemFactory()).thenReturn(mockedFactory);
when(mockedServer.isPrimaryThread()).thenReturn(true);
when(mockedFactory.getItemMeta(any(Material.class))).thenReturn(mockedMeta);
// Inject this fake server
@ -63,6 +70,6 @@ public class BukkitInitialization {
*/
public static void initializePackage() {
// Initialize reflection
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_6_R3", "org.bukkit.craftbukkit.v1_6_R3");
MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_7_R1", "org.bukkit.craftbukkit.v1_7_R1");
}
}

View File

@ -22,15 +22,15 @@ import java.lang.reflect.Array;
import java.util.List;
import java.util.UUID;
import net.minecraft.server.v1_6_R3.AttributeModifier;
import net.minecraft.server.v1_6_R3.AttributeSnapshot;
import net.minecraft.server.v1_6_R3.Packet44UpdateAttributes;
import net.minecraft.server.v1_7_R1.AttributeModifier;
import net.minecraft.server.v1_7_R1.AttributeSnapshot;
import net.minecraft.server.v1_7_R1.PacketPlayOutUpdateAttributes;
import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
// Will have to be updated for every version though
import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory;
import org.bukkit.Material;
import org.bukkit.WorldType;
@ -43,6 +43,7 @@ import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.EquivalentConverter;
@ -120,7 +121,7 @@ public class PacketContainerTest {
@Test
public void testGetShorts() {
PacketContainer itemData = new PacketContainer(Packets.Server.ITEM_DATA);
PacketContainer itemData = new PacketContainer(Packets.Server.TRANSACTION);
testPrimitive(itemData.getShorts(), 0, (short)0, (short)1);
}
@ -150,7 +151,7 @@ public class PacketContainerTest {
@Test
public void testGetStrings() {
PacketContainer explosion = new PacketContainer(Packets.Server.CHAT);
PacketContainer explosion = new PacketContainer(PacketType.Play.Client.CHAT);
testPrimitive(explosion.getStrings(), 0, null, "hello");
}
@ -183,11 +184,12 @@ public class PacketContainerTest {
StructureModifier<ItemStack> items = windowClick.getItemModifier();
ItemStack goldAxe = new ItemStack(Material.GOLD_AXE);
assertNotNull(goldAxe.getType());
assertNull(items.read(0));
// Insert the goldaxe and check if it's there
items.write(0, goldAxe);
assertTrue(equivalentItem(goldAxe, items.read(0)));
assertTrue("Item " + goldAxe + " != " + items.read(0), equivalentItem(goldAxe, items.read(0)));
}
@Test
@ -292,10 +294,12 @@ public class PacketContainerTest {
List<ChunkPosition> positions = Lists.newArrayList();
positions.add(new ChunkPosition(1, 2, 3));
positions.add(new ChunkPosition(3, 4, 5));
// Insert and read back
positionAccessor.write(0, positions);
assertEquals(positions, positionAccessor.read(0));
List<ChunkPosition> cloned = positionAccessor.read(0);
assertEquals(positions, cloned);
}
@Test
@ -319,7 +323,7 @@ public class PacketContainerTest {
@Test
public void testSerialization() {
PacketContainer chat = new PacketContainer(3);
PacketContainer chat = new PacketContainer(PacketType.Play.Client.CHAT);
chat.getStrings().write(0, "Test");
PacketContainer copy = (PacketContainer) SerializationUtils.clone(chat);
@ -337,7 +341,7 @@ public class PacketContainerTest {
List<AttributeModifier> modifiers = Lists.newArrayList(
new AttributeModifier(UUID.randomUUID(), "Unknown synced attribute modifier", 10, 0));
AttributeSnapshot snapshot = new AttributeSnapshot(
(Packet44UpdateAttributes) attribute.getHandle(), "generic.Maxhealth", 20.0, modifiers);
(PacketPlayOutUpdateAttributes) attribute.getHandle(), "generic.Maxhealth", 20.0, modifiers);
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
PacketContainer cloned = attribute.deepClone();
@ -367,25 +371,17 @@ public class PacketContainerTest {
@Test
public void testDeepClone() {
// Try constructing all the packets
for (Integer id : Iterables.concat(
Packets.getClientRegistry().values(),
Packets.getServerRegistry().values() )) {
for (PacketType type : PacketType.values()) {
// Whether or not this packet has been registered
boolean registered = Packets.Server.isSupported(id) ||
Packets.Client.isSupported(id);
boolean registered = type.isSupported();
try {
PacketContainer constructed = new PacketContainer(id);
PacketContainer constructed = new PacketContainer(type);
if (!registered) {
fail("Expected IllegalArgumentException(Packet " + id + " not registered");
fail("Expected IllegalArgumentException(Packet " + type + " not registered");
}
// Make sure these packets contains fields as well
assertTrue("Constructed packet with no known fields (" + id + ")",
constructed.getModifier().size() > 0);
// Initialize default values
constructed.getModifier().writeDefaults();
@ -396,18 +392,23 @@ public class PacketContainerTest {
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
assertEquals(firstMod.size(), secondMod.size());
// Make sure all the fields are equivalent
for (int i = 0; i < firstMod.size(); i++) {
if (firstMod.getField(i).getType().isArray())
assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i)));
else
testEquality(firstMod.read(i), secondMod.read(i));
if (PacketType.Status.Server.KICK_DISCONNECT.equals(type)) {
assertArrayEquals(SerializationUtils.serialize(constructed), SerializationUtils.serialize(cloned));
} else {
// Make sure all the fields are equivalent
for (int i = 0; i < firstMod.size(); i++) {
if (firstMod.getField(i).getType().isArray())
assertArrayEquals(getArray(firstMod.read(i)), getArray(secondMod.read(i)));
else
testEquality(firstMod.read(i), secondMod.read(i));
}
}
} catch (IllegalArgumentException e) {
if (!registered) {
// Check the same
assertEquals(e.getMessage(), "The packet ID " + id + " is not registered.");
assertEquals(e.getMessage(), "The packet ID " + type + " is not registered.");
} else {
// Something is very wrong
throw e;

View File

@ -8,10 +8,10 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import net.minecraft.server.v1_6_R3.IntHashMap;
import net.minecraft.server.v1_7_R1.IntHashMap;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory;
import org.bukkit.inventory.ItemStack;
import org.junit.BeforeClass;
import org.junit.Test;

View File

@ -4,9 +4,9 @@ import static org.junit.Assert.*;
import java.util.List;
import net.minecraft.server.v1_6_R3.AttributeModifier;
import net.minecraft.server.v1_6_R3.AttributeSnapshot;
import net.minecraft.server.v1_6_R3.Packet44UpdateAttributes;
import net.minecraft.server.v1_7_R1.AttributeModifier;
import net.minecraft.server.v1_7_R1.AttributeSnapshot;
import net.minecraft.server.v1_7_R1.PacketPlayOutUpdateAttributes;
import org.junit.Before;
import org.junit.BeforeClass;
@ -83,7 +83,7 @@ public class WrappedAttributeTest {
modifiers.add((AttributeModifier) wrapper.getHandle());
}
return new AttributeSnapshot(
(Packet44UpdateAttributes) attribute.getParentPacket().getHandle(),
(PacketPlayOutUpdateAttributes) attribute.getParentPacket().getHandle(),
attribute.getAttributeKey(), attribute.getBaseValue(), modifiers);
}

View File

@ -27,7 +27,7 @@ import java.io.DataOutput;
import java.io.DataOutputStream;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_6_R3.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_7_R1.inventory.CraftItemFactory;
import org.bukkit.inventory.ItemStack;
import org.junit.BeforeClass;
import org.junit.Test;