Updating ProtocolLib for 1.7.2.
Still a work in progress.
Dieser Commit ist enthalten in:
Ursprung
28b4874c60
Commit
c137640fee
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Game {
|
||||
public static final Protocol PROTOCOL = Protocol.GAME;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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);
|
||||
@ -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 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,10 +370,166 @@ 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.
|
||||
@ -216,12 +537,22 @@ public class PacketType implements Serializable {
|
||||
* @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 + "]";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
@ -114,11 +116,15 @@ 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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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 {
|
||||
private static final long serialVersionUID = 3;
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 2074805748222377230L;
|
||||
|
||||
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,9 +550,19 @@ public class PacketContainer implements Serializable {
|
||||
output.writeBoolean(handle != null);
|
||||
|
||||
try {
|
||||
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);
|
||||
@ -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 {
|
||||
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.
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -44,6 +44,7 @@ public interface ListenerInvoker {
|
||||
* @param packet - the packet.
|
||||
* @return The packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract int getPacketID(Object packet);
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 ID.
|
||||
* <p>
|
||||
* Note that if you pass a Class<?> as a value, it will use its type directly.
|
||||
* <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 id - packet ID.
|
||||
* @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(int id, Object[] values) {
|
||||
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;
|
||||
|
@ -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();
|
||||
@ -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();
|
||||
|
||||
|
@ -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) {
|
||||
@Deprecated
|
||||
public static StructureModifier<Object> getStructure(final int legacyId, boolean compile) {
|
||||
return getStructure(PacketType.findLegacy(legacyId), compile);
|
||||
}
|
||||
|
||||
StructureModifier<Object> result = structureModifiers.get(id);
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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()) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -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")
|
||||
@ -56,278 +46,350 @@ 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;
|
||||
// Two different packet registry
|
||||
private static volatile LegacyPacketRegistry LEGACY;
|
||||
private static volatile NettyProtocolRegistry NETTY;
|
||||
|
||||
// Fuzzy reflection
|
||||
private static FuzzyReflection packetRegistry;
|
||||
// Cached for legacy
|
||||
private static volatile Set<PacketType> NETTY_SERVER_PACKETS;
|
||||
private static volatile Set<PacketType> NETTY_CLIENT_PACKETS;
|
||||
|
||||
// The packet class to packet ID translator
|
||||
private static Map<Class, Integer> packetToID;
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
// Whether or not the registry has
|
||||
private static boolean INITIALIZED;
|
||||
|
||||
// Whether or not certain packets are sent by the client or the server
|
||||
private static ImmutableSet<Integer> serverPackets;
|
||||
private static ImmutableSet<Integer> clientPackets;
|
||||
|
||||
// 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);
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e);
|
||||
// Check for netty
|
||||
if (MinecraftReflection.isUsingNetty()) {
|
||||
if (NETTY == null) {
|
||||
NETTY = new NettyProtocolRegistry();
|
||||
}
|
||||
|
||||
// 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());
|
||||
} else {
|
||||
initializeLegacy();
|
||||
}
|
||||
});
|
||||
|
||||
// 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());
|
||||
}
|
||||
});
|
||||
}
|
||||
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);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Whatever
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class,
|
||||
Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e));
|
||||
}
|
||||
|
||||
// We'll assume this a Trove map
|
||||
return TroveWrapper.getDecoratedMap(troveMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cached fuzzy reflection instance allowing access to the packet registry.
|
||||
* @return Reflected packet registry.
|
||||
* 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.
|
||||
*/
|
||||
private static FuzzyReflection getPacketRegistry() {
|
||||
if (packetRegistry == null)
|
||||
packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true);
|
||||
return packetRegistry;
|
||||
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);
|
||||
/**
|
||||
* Retrieve every known and supported server packet type.
|
||||
* @return Every server packet type.
|
||||
*/
|
||||
public static Set<PacketType> getClientPacketTypes() {
|
||||
initialize();
|
||||
|
||||
try {
|
||||
if (sets.size() > 1) {
|
||||
serverPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(0), true);
|
||||
clientPacketsRef = (Set<Integer>) FieldUtils.readStaticField(sets.get(1), true);
|
||||
if (NETTY != null)
|
||||
return NETTY.getClientPackets();
|
||||
|
||||
// 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.");
|
||||
// Handle legacy
|
||||
if (NETTY_CLIENT_PACKETS == null) {
|
||||
NETTY_CLIENT_PACKETS = toPacketTypes(LEGACY.getClientPackets());
|
||||
}
|
||||
return NETTY_CLIENT_PACKETS;
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access field.", e);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
} 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);
|
||||
}
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -16,26 +16,13 @@ import com.google.common.collect.Sets;
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class DummyPacketInjector implements PacketInjector {
|
||||
class DummyPacketInjector extends AbstractPacketInjector implements PacketInjector {
|
||||
private SpigotPacketInjector injector;
|
||||
private IntegerSet reveivedFilters;
|
||||
|
||||
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
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,6 +118,88 @@ 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.
|
||||
*/
|
||||
@ -95,6 +207,44 @@ public class FuzzyReflection {
|
||||
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.
|
||||
* <p>
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
@ -61,4 +129,104 @@ public class MinecraftMethods {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,13 +16,17 @@ 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();
|
||||
}
|
||||
}
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
@ -204,6 +208,25 @@ public class NbtFactory {
|
||||
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.
|
||||
* @param handle - the underlying net.minecraft.server object to wrap.
|
||||
@ -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,11 +384,45 @@ 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 {
|
||||
// Delegate to the correct version
|
||||
if (methodCreateWithName)
|
||||
return createTagWithName(type, name);
|
||||
else
|
||||
return createTagSetName(type, name);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Inform the caller
|
||||
throw new FieldAccessException(
|
||||
String.format("Cannot create NBT element %s (type: %s)", name, type),
|
||||
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)
|
||||
@ -375,13 +431,19 @@ public class NbtFactory {
|
||||
return (NbtWrapper<T>) new WrappedList(handle);
|
||||
else
|
||||
return new WrappedElement<T>(handle);
|
||||
|
||||
} catch (Exception e) {
|
||||
// Inform the caller
|
||||
throw new FieldAccessException(
|
||||
String.format("Cannot create NBT element %s (type: %s)", name, type),
|
||||
e);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +70,15 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
||||
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) {
|
||||
// Enter this node?
|
||||
@ -133,12 +142,20 @@ class WrappedCompound implements NbtWrapper<Map<String, NbtBase<?>>>, Iterable<N
|
||||
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();
|
||||
|
@ -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) {
|
||||
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);
|
||||
public WrappedElement(Object handle, String name) {
|
||||
this.handle = handle;
|
||||
initializeProperty();
|
||||
setName(name);
|
||||
}
|
||||
|
||||
return baseModifier.withType(String.class);
|
||||
private void initializeProperty() {
|
||||
if (nameProperty == null) {
|
||||
Class<?> base = MinecraftReflection.getNBTBaseClass();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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);
|
||||
}
|
||||
|
@ -109,6 +109,16 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, Iterable<T
|
||||
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) {
|
||||
// Enter this node?
|
||||
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -295,7 +297,9 @@ public class PacketContainerTest {
|
||||
|
||||
// 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,6 +392,10 @@ public class PacketContainerTest {
|
||||
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
|
||||
assertEquals(firstMod.size(), secondMod.size());
|
||||
|
||||
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())
|
||||
@ -403,11 +403,12 @@ public class PacketContainerTest {
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren