Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
22cb77d78e
78
ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java
Normale Datei
78
ProtocolLib/src/com/comphenix/protocol/AsynchronousManager.java
Normale Datei
@ -0,0 +1,78 @@
|
|||||||
|
package com.comphenix.protocol;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.async.AsyncListenerHandler;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a asynchronous packet handler.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public interface AsynchronousManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an asynchronous packet handler.
|
||||||
|
* <p>
|
||||||
|
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||||
|
* @param plugin - the plugin that is registering the handler.
|
||||||
|
* @param listener - the packet listener that will recieve these asynchronous events.
|
||||||
|
* @return An asynchrouns handler.
|
||||||
|
*/
|
||||||
|
public abstract AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters and closes the given asynchronous handler.
|
||||||
|
* @param handler - asynchronous handler.
|
||||||
|
*/
|
||||||
|
public abstract void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters every asynchronous handler associated with this plugin.
|
||||||
|
* @param plugin - the original plugin.
|
||||||
|
*/
|
||||||
|
public void unregisterAsyncHandlers(Plugin plugin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a immutable set containing the ID of the sent server packets that will be
|
||||||
|
* observed by the asynchronous listeners.
|
||||||
|
* @return Every filtered server packet.
|
||||||
|
*/
|
||||||
|
public abstract Set<Integer> getSendingFilters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a immutable set containing the ID of the recieved client packets that will be
|
||||||
|
* observed by the asynchronous listeners.
|
||||||
|
* @return Every filtered client packet.
|
||||||
|
*/
|
||||||
|
public abstract Set<Integer> getReceivingFilters();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a given synchronous packet has asynchronous listeners.
|
||||||
|
* @param packet - packet to test.
|
||||||
|
* @return TRUE if it does, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public abstract boolean hasAsynchronousListeners(PacketEvent packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the default packet stream.
|
||||||
|
* @return Default packet stream.
|
||||||
|
*/
|
||||||
|
public abstract PacketStream getPacketStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the default error logger.
|
||||||
|
* @return Default logger.
|
||||||
|
*/
|
||||||
|
public abstract Logger getLogger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove listeners, close threads and transmit every delayed packet.
|
||||||
|
*/
|
||||||
|
public abstract void cleanupAll();
|
||||||
|
}
|
55
ProtocolLib/src/com/comphenix/protocol/PacketStream.java
Normale Datei
55
ProtocolLib/src/com/comphenix/protocol/PacketStream.java
Normale Datei
@ -0,0 +1,55 @@
|
|||||||
|
package com.comphenix.protocol;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a object capable of sending or receiving packets.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public interface PacketStream {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a packet to the given player.
|
||||||
|
* @param reciever - the reciever.
|
||||||
|
* @param packet - packet to send.
|
||||||
|
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||||
|
*/
|
||||||
|
public void sendServerPacket(Player reciever, PacketContainer packet)
|
||||||
|
throws InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a packet to the given player.
|
||||||
|
* @param reciever - the reciever.
|
||||||
|
* @param packet - packet to send.
|
||||||
|
* @param filters - whether or not to invoke any packet filters.
|
||||||
|
* @throws InvocationTargetException - if an error occured when sending the packet.
|
||||||
|
*/
|
||||||
|
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||||
|
throws InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate recieving a certain packet from a given player.
|
||||||
|
* @param sender - the sender.
|
||||||
|
* @param packet - the packet that was sent.
|
||||||
|
* @throws InvocationTargetException If the reflection machinery failed.
|
||||||
|
* @throws IllegalAccessException If the underlying method caused an error.
|
||||||
|
*/
|
||||||
|
public void recieveClientPacket(Player sender, PacketContainer packet)
|
||||||
|
throws IllegalAccessException, InvocationTargetException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulate recieving a certain packet from a given player.
|
||||||
|
* @param sender - the sender.
|
||||||
|
* @param packet - the packet that was sent.
|
||||||
|
* @param filters - whether or not to invoke any packet filters.
|
||||||
|
* @throws InvocationTargetException If the reflection machinery failed.
|
||||||
|
* @throws IllegalAccessException If the underlying method caused an error.
|
||||||
|
*/
|
||||||
|
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
||||||
|
throws IllegalAccessException, InvocationTargetException;
|
||||||
|
}
|
@ -25,8 +25,10 @@ import org.bukkit.Server;
|
|||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||||
import com.comphenix.protocol.metrics.Statistics;
|
import com.comphenix.protocol.metrics.Statistics;
|
||||||
|
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||||
|
|
||||||
public class ProtocolLibrary extends JavaPlugin {
|
public class ProtocolLibrary extends JavaPlugin {
|
||||||
|
|
||||||
@ -39,10 +41,19 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
// Metrics and statistisc
|
// Metrics and statistisc
|
||||||
private Statistics statistisc;
|
private Statistics statistisc;
|
||||||
|
|
||||||
|
// Structure compiler
|
||||||
|
private BackgroundCompiler backgroundCompiler;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
logger = getLoggerSafely();
|
logger = getLoggerSafely();
|
||||||
protocolManager = new PacketFilterManager(getClassLoader(), logger);
|
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,6 +61,12 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
Server server = getServer();
|
Server server = getServer();
|
||||||
PluginManager manager = server.getPluginManager();
|
PluginManager manager = server.getPluginManager();
|
||||||
|
|
||||||
|
// Initialize background compiler
|
||||||
|
if (backgroundCompiler == null) {
|
||||||
|
backgroundCompiler = new BackgroundCompiler(getClassLoader());
|
||||||
|
BackgroundCompiler.setInstance(backgroundCompiler);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify server managers of incompatible plugins
|
// Notify server managers of incompatible plugins
|
||||||
checkForIncompatibility(manager);
|
checkForIncompatibility(manager);
|
||||||
|
|
||||||
@ -59,6 +76,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
// Inject our hook into already existing players
|
// Inject our hook into already existing players
|
||||||
protocolManager.initializePlayers(server.getOnlinePlayers());
|
protocolManager.initializePlayers(server.getOnlinePlayers());
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
createAsyncTask(server);
|
||||||
|
|
||||||
// Try to enable statistics
|
// Try to enable statistics
|
||||||
try {
|
try {
|
||||||
statistisc = new Statistics(this);
|
statistisc = new Statistics(this);
|
||||||
@ -69,9 +89,32 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createAsyncTask(Server server) {
|
||||||
|
try {
|
||||||
|
if (asyncPacketTask >= 0)
|
||||||
|
throw new IllegalStateException("Async task has already been created");
|
||||||
|
|
||||||
|
// Attempt to create task
|
||||||
|
asyncPacketTask = server.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
AsyncFilterManager manager = (AsyncFilterManager) protocolManager.getAsynchronousManager();
|
||||||
|
|
||||||
|
// We KNOW we're on the main thread at the moment
|
||||||
|
manager.sendProcessedPackets(tickCounter++, true);
|
||||||
|
}
|
||||||
|
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (asyncPacketTask == -1) {
|
||||||
|
logger.log(Level.SEVERE, "Unable to create packet timeout task.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkForIncompatibility(PluginManager manager) {
|
private void checkForIncompatibility(PluginManager manager) {
|
||||||
// Plugin authors: Notify me to remove these
|
// Plugin authors: Notify me to remove these
|
||||||
String[] incompatiblePlugins = { "TagAPI" };
|
String[] incompatiblePlugins = {};
|
||||||
|
|
||||||
for (String plugin : incompatiblePlugins) {
|
for (String plugin : incompatiblePlugins) {
|
||||||
if (manager.getPlugin(plugin) != null) {
|
if (manager.getPlugin(plugin) != null) {
|
||||||
@ -83,6 +126,19 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
|
// Disable compiler
|
||||||
|
if (backgroundCompiler != null) {
|
||||||
|
backgroundCompiler.shutdownAll();
|
||||||
|
backgroundCompiler = null;
|
||||||
|
BackgroundCompiler.setInstance(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
if (asyncPacketTask >= 0) {
|
||||||
|
getServer().getScheduler().cancelTask(asyncPacketTask);
|
||||||
|
asyncPacketTask = -1;
|
||||||
|
}
|
||||||
|
|
||||||
protocolManager.close();
|
protocolManager.close();
|
||||||
protocolManager = null;
|
protocolManager = null;
|
||||||
statistisc = null;
|
statistisc = null;
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol;
|
package com.comphenix.protocol;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ import com.google.common.collect.ImmutableSet;
|
|||||||
* Represents an API for accessing the Minecraft protocol.
|
* Represents an API for accessing the Minecraft protocol.
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public interface ProtocolManager {
|
public interface ProtocolManager extends PacketStream {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of every registered packet listener.
|
* Retrieves a list of every registered packet listener.
|
||||||
@ -66,46 +65,6 @@ public interface ProtocolManager {
|
|||||||
* @param plugin - the plugin to unload.
|
* @param plugin - the plugin to unload.
|
||||||
*/
|
*/
|
||||||
public void removePacketListeners(Plugin plugin);
|
public void removePacketListeners(Plugin plugin);
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a packet to the given player.
|
|
||||||
* @param reciever - the reciever.
|
|
||||||
* @param packet - packet to send.
|
|
||||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
|
||||||
*/
|
|
||||||
public void sendServerPacket(Player reciever, PacketContainer packet)
|
|
||||||
throws InvocationTargetException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a packet to the given player.
|
|
||||||
* @param reciever - the reciever.
|
|
||||||
* @param packet - packet to send.
|
|
||||||
* @param filters - whether or not to invoke any packet filters.
|
|
||||||
* @throws InvocationTargetException - if an error occured when sending the packet.
|
|
||||||
*/
|
|
||||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
|
||||||
throws InvocationTargetException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simulate recieving a certain packet from a given player.
|
|
||||||
* @param sender - the sender.
|
|
||||||
* @param packet - the packet that was sent.
|
|
||||||
* @throws InvocationTargetException If the reflection machinery failed.
|
|
||||||
* @throws IllegalAccessException If the underlying method caused an error.
|
|
||||||
*/
|
|
||||||
public void recieveClientPacket(Player sender, PacketContainer packet)
|
|
||||||
throws IllegalAccessException, InvocationTargetException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simulate recieving a certain packet from a given player.
|
|
||||||
* @param sender - the sender.
|
|
||||||
* @param packet - the packet that was sent.
|
|
||||||
* @param filters - whether or not to invoke any packet filters.
|
|
||||||
* @throws InvocationTargetException If the reflection machinery failed.
|
|
||||||
* @throws IllegalAccessException If the underlying method caused an error.
|
|
||||||
*/
|
|
||||||
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters)
|
|
||||||
throws IllegalAccessException, InvocationTargetException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||||
@ -163,4 +122,10 @@ public interface ProtocolManager {
|
|||||||
* @return TRUE if it has, FALSE otherwise.
|
* @return TRUE if it has, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isClosed();
|
public boolean isClosed();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current asyncronous packet manager.
|
||||||
|
* @return Asyncronous packet manager.
|
||||||
|
*/
|
||||||
|
public AsynchronousManager getAsynchronousManager();
|
||||||
}
|
}
|
301
ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java
Normale Datei
301
ProtocolLib/src/com/comphenix/protocol/async/AsyncFilterManager.java
Normale Datei
@ -0,0 +1,301 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.AsynchronousManager;
|
||||||
|
import com.comphenix.protocol.PacketStream;
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||||
|
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a filter manager for asynchronous packets.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class AsyncFilterManager implements AsynchronousManager {
|
||||||
|
|
||||||
|
private PacketProcessingQueue serverProcessingQueue;
|
||||||
|
private PacketSendingQueue serverQueue;
|
||||||
|
|
||||||
|
private PacketProcessingQueue clientProcessingQueue;
|
||||||
|
private PacketSendingQueue clientQueue;
|
||||||
|
|
||||||
|
private Logger logger;
|
||||||
|
|
||||||
|
// The likely main thread
|
||||||
|
private Thread mainThread;
|
||||||
|
|
||||||
|
// Default scheduler
|
||||||
|
private BukkitScheduler scheduler;
|
||||||
|
|
||||||
|
// Our protocol manager
|
||||||
|
private ProtocolManager manager;
|
||||||
|
|
||||||
|
// Current packet index
|
||||||
|
private AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||||
|
|
||||||
|
// Whether or not we're currently cleaning up
|
||||||
|
private volatile boolean cleaningUp;
|
||||||
|
|
||||||
|
public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) {
|
||||||
|
|
||||||
|
// Server packets are synchronized already
|
||||||
|
this.serverQueue = new PacketSendingQueue(false);
|
||||||
|
// Client packets must be synchronized
|
||||||
|
this.clientQueue = new PacketSendingQueue(true);
|
||||||
|
|
||||||
|
this.serverProcessingQueue = new PacketProcessingQueue(serverQueue);
|
||||||
|
this.clientProcessingQueue = new PacketProcessingQueue(clientQueue);
|
||||||
|
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
this.manager = manager;
|
||||||
|
|
||||||
|
this.logger = logger;
|
||||||
|
this.mainThread = Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||||
|
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
|
||||||
|
|
||||||
|
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||||
|
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||||
|
|
||||||
|
// We need a synchronized listener to get the ball rolling
|
||||||
|
boolean hasListener = true;
|
||||||
|
|
||||||
|
// Add listener to either or both processing queue
|
||||||
|
if (hasValidWhitelist(sendingWhitelist)) {
|
||||||
|
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
|
||||||
|
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||||
|
hasListener &= hasPacketListener(sendingWhitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidWhitelist(receivingWhitelist)) {
|
||||||
|
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
|
||||||
|
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||||
|
hasListener &= hasPacketListener(receivingWhitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasListener) {
|
||||||
|
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||||
|
manager.addPacketListener(handler.getNullPacketListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given packets are represented.
|
||||||
|
* @param whitelist - list of packets.
|
||||||
|
* @return TRUE if they are all registered, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private boolean hasPacketListener(ListeningWhitelist whitelist) {
|
||||||
|
return manager.getSendingFilters().containsAll(whitelist.getWhitelist());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||||
|
return whitelist != null && whitelist.getWhitelist().size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
|
||||||
|
if (handler == null)
|
||||||
|
throw new IllegalArgumentException("listenerToken cannot be NULL");
|
||||||
|
|
||||||
|
handler.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by AsyncListenerHandler
|
||||||
|
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
|
||||||
|
|
||||||
|
PacketListener listener = handler.getAsyncListener();
|
||||||
|
boolean synchronusOK = onMainThread();
|
||||||
|
|
||||||
|
// Unregister null packet listeners
|
||||||
|
if (handler.getNullPacketListener() != null) {
|
||||||
|
manager.removePacketListener(handler.getNullPacketListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just remove it from the queue(s)
|
||||||
|
if (hasValidWhitelist(listener.getSendingWhitelist())) {
|
||||||
|
List<Integer> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||||
|
|
||||||
|
// We're already taking care of this, so don't do anything
|
||||||
|
if (!cleaningUp)
|
||||||
|
serverQueue.signalPacketUpdate(removed, synchronusOK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||||
|
List<Integer> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||||
|
|
||||||
|
if (!cleaningUp)
|
||||||
|
clientQueue.signalPacketUpdate(removed, synchronusOK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if we're running on the main thread.
|
||||||
|
* @return TRUE if we are, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private boolean onMainThread() {
|
||||||
|
return Thread.currentThread().getId() == mainThread.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterAsyncHandlers(Plugin plugin) {
|
||||||
|
unregisterAsyncHandlers(serverProcessingQueue, plugin);
|
||||||
|
unregisterAsyncHandlers(clientProcessingQueue, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||||
|
|
||||||
|
// Iterate through every packet listener
|
||||||
|
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||||
|
// Remove the listener
|
||||||
|
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||||
|
unregisterAsyncHandler(listener.getListener());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue a packet for asynchronous processing.
|
||||||
|
* @param syncPacket - synchronous packet event.
|
||||||
|
* @param asyncMarker - the asynchronous marker to use.
|
||||||
|
*/
|
||||||
|
public void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
|
||||||
|
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
|
||||||
|
|
||||||
|
// Start the process
|
||||||
|
getSendingQueue(syncPacket).enqueue(newEvent);
|
||||||
|
|
||||||
|
// We know this is occuring on the main thread, so pass TRUE
|
||||||
|
getProcessingQueue(syncPacket).enqueue(newEvent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Integer> getSendingFilters() {
|
||||||
|
return serverProcessingQueue.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Integer> getReceivingFilters() {
|
||||||
|
return clientProcessingQueue.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to create a default asynchronous task.
|
||||||
|
* @param plugin - the calling plugin.
|
||||||
|
* @param runnable - the runnable.
|
||||||
|
*/
|
||||||
|
public void scheduleAsyncTask(Plugin plugin, Runnable runnable) {
|
||||||
|
scheduler.scheduleAsyncDelayedTask(plugin, runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||||
|
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketID());
|
||||||
|
return list != null && list.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a asynchronous marker with all the default values.
|
||||||
|
* @return Asynchronous marker.
|
||||||
|
*/
|
||||||
|
public AsyncMarker createAsyncMarker() {
|
||||||
|
return createAsyncMarker(AsyncMarker.DEFAULT_SENDING_DELTA, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an async marker with the given sending priority delta and timeout delta.
|
||||||
|
* @param sendingDelta - how many packets we're willing to wait.
|
||||||
|
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||||
|
* @return An async marker.
|
||||||
|
*/
|
||||||
|
public AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta) {
|
||||||
|
return createAsyncMarker(sendingDelta, timeoutDelta,
|
||||||
|
currentSendingIndex.incrementAndGet(), System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method
|
||||||
|
private AsyncMarker createAsyncMarker(long sendingDelta, long timeoutDelta, long sendingIndex, long currentTime) {
|
||||||
|
return new AsyncMarker(manager, sendingIndex, sendingDelta, System.currentTimeMillis(), timeoutDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketStream getPacketStream() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Logger getLogger() {
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanupAll() {
|
||||||
|
cleaningUp = true;
|
||||||
|
serverProcessingQueue.cleanupAll();
|
||||||
|
serverQueue.cleanupAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a packet is ready to be transmitted.
|
||||||
|
* @param packet - packet to signal.
|
||||||
|
*/
|
||||||
|
public void signalPacketUpdate(PacketEvent packet) {
|
||||||
|
getSendingQueue(packet).signalPacketUpdate(packet, onMainThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the sending queue this packet belongs to.
|
||||||
|
* @param packet - the packet.
|
||||||
|
* @return The server or client sending queue the packet belongs to.
|
||||||
|
*/
|
||||||
|
private PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||||
|
return packet.isServerPacket() ? serverQueue : clientQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal that a packet has finished processing.
|
||||||
|
* @param packet - packet to signal.
|
||||||
|
*/
|
||||||
|
public void signalProcessingDone(PacketEvent packet) {
|
||||||
|
getProcessingQueue(packet).signalProcessingDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the processing queue this packet belongs to.
|
||||||
|
* @param packet - the packet.
|
||||||
|
* @return The server or client sending processing the packet belongs to.
|
||||||
|
*/
|
||||||
|
private PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
||||||
|
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send any due packets, or clean up packets that have expired.
|
||||||
|
*/
|
||||||
|
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||||
|
|
||||||
|
// The server queue is unlikely to need checking that often
|
||||||
|
if (tickCounter % 10 == 0) {
|
||||||
|
serverQueue.trySendPackets(onMainThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
clientQueue.trySendPackets(onMainThread);
|
||||||
|
}
|
||||||
|
}
|
210
ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java
Normale Datei
210
ProtocolLib/src/com/comphenix/protocol/async/AsyncListenerHandler.java
Normale Datei
@ -0,0 +1,210 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a handler for an asynchronous event.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class AsyncListenerHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal an end to the packet processing.
|
||||||
|
*/
|
||||||
|
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
|
||||||
|
|
||||||
|
// Default queue capacity
|
||||||
|
private static int DEFAULT_CAPACITY = 1024;
|
||||||
|
|
||||||
|
// Cancel the async handler
|
||||||
|
private volatile boolean cancelled;
|
||||||
|
|
||||||
|
// If we've started the listener loop before
|
||||||
|
private volatile boolean started;
|
||||||
|
|
||||||
|
// The packet listener
|
||||||
|
private PacketListener listener;
|
||||||
|
|
||||||
|
// The filter manager
|
||||||
|
private AsyncFilterManager filterManager;
|
||||||
|
private NullPacketListener nullPacketListener;
|
||||||
|
|
||||||
|
// List of queued packets
|
||||||
|
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
|
||||||
|
|
||||||
|
// Minecraft main thread
|
||||||
|
private Thread mainThread;
|
||||||
|
|
||||||
|
public AsyncListenerHandler(Thread mainThread, AsyncFilterManager filterManager, PacketListener listener) {
|
||||||
|
if (filterManager == null)
|
||||||
|
throw new IllegalArgumentException("filterManager cannot be NULL");
|
||||||
|
if (listener == null)
|
||||||
|
throw new IllegalArgumentException("listener cannot be NULL");
|
||||||
|
|
||||||
|
this.mainThread = mainThread;
|
||||||
|
this.filterManager = filterManager;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketListener getAsyncListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the synchronized listener that has been automatically created.
|
||||||
|
* @param nullPacketListener - automatically created listener.
|
||||||
|
*/
|
||||||
|
void setNullPacketListener(NullPacketListener nullPacketListener) {
|
||||||
|
this.nullPacketListener = nullPacketListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the synchronized listener that was automatically created.
|
||||||
|
* @return Automatically created listener.
|
||||||
|
*/
|
||||||
|
PacketListener getNullPacketListener() {
|
||||||
|
return nullPacketListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the handler.
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
// Remove the listener as quickly as possible
|
||||||
|
close();
|
||||||
|
|
||||||
|
// Poison Pill Shutdown
|
||||||
|
queuedPackets.clear();
|
||||||
|
queuedPackets.add(INTERUPT_PACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue a packet for processing.
|
||||||
|
* @param packet - a packet for processing.
|
||||||
|
* @throws IllegalStateException If the underlying packet queue is full.
|
||||||
|
*/
|
||||||
|
public void enqueuePacket(PacketEvent packet) {
|
||||||
|
if (packet == null)
|
||||||
|
throw new IllegalArgumentException("packet is NULL");
|
||||||
|
|
||||||
|
queuedPackets.add(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a runnable that will initiate the listener loop.
|
||||||
|
* <p>
|
||||||
|
* <b>Warning</b>: Never call the run() method in the main thread.
|
||||||
|
*/
|
||||||
|
public Runnable getListenerLoop() {
|
||||||
|
return new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
listenerLoop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO NOT call this method from the main thread
|
||||||
|
private void listenerLoop() {
|
||||||
|
|
||||||
|
// Danger, danger!
|
||||||
|
if (Thread.currentThread().getId() == mainThread.getId())
|
||||||
|
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||||
|
if (started)
|
||||||
|
throw new IllegalStateException("A listener cannot be run by multiple threads. Create a new listener instead.");
|
||||||
|
if (cancelled)
|
||||||
|
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||||
|
|
||||||
|
// Proceed
|
||||||
|
started = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mainLoop:
|
||||||
|
while (!cancelled) {
|
||||||
|
PacketEvent packet = queuedPackets.take();
|
||||||
|
AsyncMarker marker = packet.getAsyncMarker();
|
||||||
|
|
||||||
|
// Handle cancel requests
|
||||||
|
if (packet == null || marker == null || !packet.isAsynchronous()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here's the core of the asynchronous processing
|
||||||
|
try {
|
||||||
|
if (packet.isServerPacket())
|
||||||
|
listener.onPacketSending(packet);
|
||||||
|
else
|
||||||
|
listener.onPacketReceiving(packet);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Minecraft doesn't want your Exception.
|
||||||
|
filterManager.getLogger().log(Level.SEVERE,
|
||||||
|
"Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, get the next non-cancelled listener
|
||||||
|
for (; marker.getListenerTraversal().hasNext(); ) {
|
||||||
|
AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener();
|
||||||
|
|
||||||
|
if (!handler.isCancelled()) {
|
||||||
|
handler.enqueuePacket(packet);
|
||||||
|
continue mainLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are no more listeners - queue the packet for transmission
|
||||||
|
filterManager.signalPacketUpdate(packet);
|
||||||
|
filterManager.signalProcessingDone(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// We're done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close() {
|
||||||
|
// Remove the listener itself
|
||||||
|
if (!cancelled) {
|
||||||
|
filterManager.unregisterAsyncHandlerInternal(this);
|
||||||
|
cancelled = true;
|
||||||
|
started = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPluginName() {
|
||||||
|
return PacketAdapter.getPluginName(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the plugin associated with this async listener.
|
||||||
|
* @return The plugin.
|
||||||
|
*/
|
||||||
|
public Plugin getPlugin() {
|
||||||
|
return listener != null ? listener.getPlugin() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the asynchronous listener using the Bukkit scheduler.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
if (listener.getPlugin() == null)
|
||||||
|
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||||
|
|
||||||
|
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
|
||||||
|
}
|
||||||
|
}
|
305
ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java
Normale Datei
305
ProtocolLib/src/com/comphenix/protocol/async/AsyncMarker.java
Normale Datei
@ -0,0 +1,305 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.minecraft.server.Packet;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.PacketStream;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information about the packet that is being processed by asynchronous listeners.
|
||||||
|
* <p>
|
||||||
|
* Asynchronous listeners can use this to set packet timeout or transmission order.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated by Eclipse.
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = -2621498096616187384L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default number of milliseconds until a packet will rejected.
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_TIMEOUT_DELTA = 60000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default number of packets to skip.
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_SENDING_DELTA = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||||
|
*/
|
||||||
|
private transient PacketStream packetStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current list of async packet listeners.
|
||||||
|
*/
|
||||||
|
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||||
|
|
||||||
|
// Timeout handling
|
||||||
|
private long initialTime;
|
||||||
|
private long timeout;
|
||||||
|
|
||||||
|
// Packet order
|
||||||
|
private long originalSendingIndex;
|
||||||
|
private long newSendingIndex;
|
||||||
|
|
||||||
|
// Whether or not the packet has been processed by the listeners
|
||||||
|
private volatile boolean processed;
|
||||||
|
|
||||||
|
// Whether or not the packet has been sent
|
||||||
|
private volatile boolean transmitted;
|
||||||
|
|
||||||
|
// Whether or not the asynchronous processing itself should be cancelled
|
||||||
|
private volatile boolean asyncCancelled;
|
||||||
|
|
||||||
|
// Determine if Minecraft processes this packet asynchronously
|
||||||
|
private static Method isMinecraftAsync;
|
||||||
|
private static boolean alwaysSync;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a container for asyncronous packets.
|
||||||
|
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||||
|
*/
|
||||||
|
AsyncMarker(PacketStream packetStream, long sendingIndex, long sendingDelta, long initialTime, long timeoutDelta) {
|
||||||
|
if (packetStream == null)
|
||||||
|
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||||
|
|
||||||
|
this.packetStream = packetStream;
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
this.initialTime = initialTime;
|
||||||
|
this.timeout = initialTime + timeoutDelta;
|
||||||
|
|
||||||
|
// Sending index
|
||||||
|
this.originalSendingIndex = sendingIndex;
|
||||||
|
this.newSendingIndex = sendingIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the time the packet was initially queued for asynchronous processing.
|
||||||
|
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
|
||||||
|
*/
|
||||||
|
public long getInitialTime() {
|
||||||
|
return initialTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the time the packet will be forcefully rejected.
|
||||||
|
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||||
|
*/
|
||||||
|
public long getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time the packet will be forcefully rejected.
|
||||||
|
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||||
|
*/
|
||||||
|
public void setTimeout(long timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the order the packet was originally transmitted.
|
||||||
|
* @return The original packet index.
|
||||||
|
*/
|
||||||
|
public long getOriginalSendingIndex() {
|
||||||
|
return originalSendingIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the desired sending order after processing has completed.
|
||||||
|
* <p>
|
||||||
|
* Higher sending order means lower priority.
|
||||||
|
* @return Desired sending order.
|
||||||
|
*/
|
||||||
|
public long getNewSendingIndex() {
|
||||||
|
return newSendingIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the desired sending order after processing has completed.
|
||||||
|
* <p>
|
||||||
|
* Higher sending order means lower priority.
|
||||||
|
* @param newSendingIndex - new packet send index.
|
||||||
|
*/
|
||||||
|
public void setNewSendingIndex(long newSendingIndex) {
|
||||||
|
this.newSendingIndex = newSendingIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the packet stream responsible for transmitting this packet.
|
||||||
|
* @return The packet stream.
|
||||||
|
*/
|
||||||
|
public PacketStream getPacketStream() {
|
||||||
|
return packetStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the output packet stream responsible for transmitting this packet.
|
||||||
|
* @param packetStream - new output packet stream.
|
||||||
|
*/
|
||||||
|
public void setPacketStream(PacketStream packetStream) {
|
||||||
|
this.packetStream = packetStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether or not this packet has been processed by the async listeners.
|
||||||
|
* @return TRUE if it has been processed, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isProcessed() {
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not this packet has been processed by the async listeners.
|
||||||
|
* @param processed - TRUE if it has, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
void setProcessed(boolean processed) {
|
||||||
|
this.processed = processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether or not this packet has already been sent.
|
||||||
|
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isTransmitted() {
|
||||||
|
return transmitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this packet has expired.
|
||||||
|
* @return TRUE if it has, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasExpired() {
|
||||||
|
return hasExpired(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this packet has expired given this time.
|
||||||
|
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||||
|
* @return TRUE if it has, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasExpired(long currentTime) {
|
||||||
|
return timeout < currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the asynchronous handling should be cancelled.
|
||||||
|
* @return TRUE if it should, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isAsyncCancelled() {
|
||||||
|
return asyncCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether or not the asynchronous handling should be cancelled.
|
||||||
|
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public void setAsyncCancelled(boolean asyncCancelled) {
|
||||||
|
this.asyncCancelled = asyncCancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve iterator for the next listener in line.
|
||||||
|
* @return Next async packet listener iterator.
|
||||||
|
*/
|
||||||
|
public Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||||
|
return listenerTraversal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the iterator for the next listener.
|
||||||
|
* @param listenerTraversal - the new async packet listener iterator.
|
||||||
|
*/
|
||||||
|
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||||
|
this.listenerTraversal = listenerTraversal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit a given packet to the current packet stream.
|
||||||
|
* @param event - the packet to send.
|
||||||
|
* @throws IOException If the packet couldn't be sent.
|
||||||
|
*/
|
||||||
|
public void sendPacket(PacketEvent event) throws IOException {
|
||||||
|
try {
|
||||||
|
if (event.isServerPacket()) {
|
||||||
|
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false);
|
||||||
|
} else {
|
||||||
|
packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false);
|
||||||
|
}
|
||||||
|
transmitted = true;
|
||||||
|
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new IOException("Cannot send packet", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IOException("Cannot send packet", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if Minecraft allows asynchronous processing of this packet.
|
||||||
|
* @return TRUE if it does, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
|
||||||
|
|
||||||
|
if (isMinecraftAsync == null && !alwaysSync) {
|
||||||
|
try {
|
||||||
|
isMinecraftAsync = FuzzyReflection.fromClass(Packet.class).getMethodByName("a_.*");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// This will occur in 1.2.5 (or possibly in later versions)
|
||||||
|
List<Method> methods = FuzzyReflection.fromClass(Packet.class).
|
||||||
|
getMethodListByParameters(boolean.class, new Class[] {});
|
||||||
|
|
||||||
|
// Try to look for boolean methods
|
||||||
|
if (methods.size() == 2) {
|
||||||
|
isMinecraftAsync = methods.get(1);
|
||||||
|
} else if (methods.size() == 1) {
|
||||||
|
// We're in 1.2.5
|
||||||
|
alwaysSync = true;
|
||||||
|
} else {
|
||||||
|
System.err.println("Cannot determine asynchronous state of packets!");
|
||||||
|
alwaysSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alwaysSync) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Wrap exceptions
|
||||||
|
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new FieldAccessException("Illegal argument", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new FieldAccessException("Minecraft error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(AsyncMarker o) {
|
||||||
|
if (o == null)
|
||||||
|
return 1;
|
||||||
|
else
|
||||||
|
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||||
|
}
|
||||||
|
}
|
62
ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java
Normale Datei
62
ProtocolLib/src/com/comphenix/protocol/async/NullPacketListener.java
Normale Datei
@ -0,0 +1,62 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.ListenerPriority;
|
||||||
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a NO OPERATION listener.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class NullPacketListener implements PacketListener {
|
||||||
|
|
||||||
|
private ListeningWhitelist sendingWhitelist;
|
||||||
|
private ListeningWhitelist receivingWhitelist;
|
||||||
|
private Plugin plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a no-op listener with the same whitelist and plugin as the given listener.
|
||||||
|
* @param original - the packet listener to copy.
|
||||||
|
*/
|
||||||
|
public NullPacketListener(PacketListener original) {
|
||||||
|
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
|
||||||
|
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
|
||||||
|
this.plugin = original.getPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPacketSending(PacketEvent event) {
|
||||||
|
// NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPacketReceiving(PacketEvent event) {
|
||||||
|
// NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListeningWhitelist getSendingWhitelist() {
|
||||||
|
return sendingWhitelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListeningWhitelist getReceivingWhitelist() {
|
||||||
|
return receivingWhitelist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
|
||||||
|
if (whitelist != null)
|
||||||
|
return new ListeningWhitelist(priority, whitelist.getWhitelist());
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Plugin getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
}
|
133
ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java
Normale Datei
133
ProtocolLib/src/com/comphenix/protocol/async/PacketProcessingQueue.java
Normale Datei
@ -0,0 +1,133 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the processing of every packet type.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default maximum number of packets to process concurrently.
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default maximum number of packets to queue for processing.
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of packets we're processing concurrently.
|
||||||
|
*/
|
||||||
|
private final int maximumConcurrency;
|
||||||
|
private Semaphore concurrentProcessing;
|
||||||
|
|
||||||
|
// Queued packets for being processed
|
||||||
|
private ArrayBlockingQueue<PacketEvent> processingQueue;
|
||||||
|
|
||||||
|
// Packets for sending
|
||||||
|
private PacketSendingQueue sendingQueue;
|
||||||
|
|
||||||
|
public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
|
||||||
|
this(sendingQueue, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int queueLimit, int maximumConcurrency) {
|
||||||
|
super();
|
||||||
|
this.processingQueue = new ArrayBlockingQueue<PacketEvent>(queueLimit);
|
||||||
|
this.maximumConcurrency = maximumConcurrency;
|
||||||
|
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||||
|
this.sendingQueue = sendingQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue a packet for processing by the asynchronous listeners.
|
||||||
|
* @param packet - packet to process.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
|
||||||
|
*/
|
||||||
|
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||||
|
try {
|
||||||
|
processingQueue.add(packet);
|
||||||
|
|
||||||
|
// Begin processing packets
|
||||||
|
signalBeginProcessing(onMainThread);
|
||||||
|
return true;
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the current method and each thread to signal that a packet might be ready for processing.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
*/
|
||||||
|
public void signalBeginProcessing(boolean onMainThread) {
|
||||||
|
while (concurrentProcessing.tryAcquire()) {
|
||||||
|
PacketEvent packet = processingQueue.poll();
|
||||||
|
|
||||||
|
// Any packet queued?
|
||||||
|
if (packet != null) {
|
||||||
|
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
|
||||||
|
AsyncMarker marker = packet.getAsyncMarker();
|
||||||
|
|
||||||
|
// Yes, removing the marker will cause the chain to stop
|
||||||
|
if (list != null) {
|
||||||
|
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||||
|
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
marker.setListenerTraversal(iterator);
|
||||||
|
iterator.next().getListener().enqueuePacket(packet);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The packet has no further listeners. Just send it.
|
||||||
|
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||||
|
signalProcessingDone();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// No more queued packets.
|
||||||
|
signalProcessingDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a packet has been processed.
|
||||||
|
*/
|
||||||
|
public void signalProcessingDone() {
|
||||||
|
concurrentProcessing.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the maximum number of packets to process at any given time.
|
||||||
|
* @return Number of simultaneous packet to process.
|
||||||
|
*/
|
||||||
|
public int getMaximumConcurrency() {
|
||||||
|
return maximumConcurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanupAll() {
|
||||||
|
// Cancel all the threads and every listener
|
||||||
|
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||||
|
if (handler != null) {
|
||||||
|
handler.getListener().cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the rest, just in case
|
||||||
|
clearListeners();
|
||||||
|
}
|
||||||
|
}
|
177
ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java
Normale Datei
177
ProtocolLib/src/com/comphenix/protocol/async/PacketSendingQueue.java
Normale Datei
@ -0,0 +1,177 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents packets ready to be transmitted to a client.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class PacketSendingQueue {
|
||||||
|
|
||||||
|
private static final int INITIAL_CAPACITY = 64;
|
||||||
|
|
||||||
|
private PriorityBlockingQueue<PacketEvent> sendingQueue;
|
||||||
|
|
||||||
|
// Whether or not packet transmission can only occur on the main thread
|
||||||
|
private final boolean synchronizeMain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a packet sending queue.
|
||||||
|
* @param synchronizeMain - whether or not to synchronize with the main thread.
|
||||||
|
*/
|
||||||
|
public PacketSendingQueue(boolean synchronizeMain) {
|
||||||
|
this.synchronizeMain = synchronizeMain;
|
||||||
|
this.sendingQueue = new PriorityBlockingQueue<PacketEvent>(INITIAL_CAPACITY, new Comparator<PacketEvent>() {
|
||||||
|
// Compare using the async marker
|
||||||
|
@Override
|
||||||
|
public int compare(PacketEvent o1, PacketEvent o2) {
|
||||||
|
return ComparisonChain.start().
|
||||||
|
compare(o1.getAsyncMarker(), o2.getAsyncMarker()).
|
||||||
|
result();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue a packet for sending.
|
||||||
|
* @param packet
|
||||||
|
*/
|
||||||
|
public void enqueue(PacketEvent packet) {
|
||||||
|
sendingQueue.add(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when one of the packets have finished processing.
|
||||||
|
* @param packetUpdated - the packet that has now been updated.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
*/
|
||||||
|
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||||
|
// Mark this packet as finished
|
||||||
|
packetUpdated.getAsyncMarker().setProcessed(true);
|
||||||
|
trySendPackets(onMainThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||||
|
* @param packetsRemoved - packets that no longer have any listeners.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
*/
|
||||||
|
public synchronized void signalPacketUpdate(List<Integer> packetsRemoved, boolean onMainThread) {
|
||||||
|
|
||||||
|
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
|
||||||
|
|
||||||
|
// Note that this is O(n), so it might be expensive
|
||||||
|
for (PacketEvent event : sendingQueue) {
|
||||||
|
if (lookup.contains(event.getPacketID())) {
|
||||||
|
event.getAsyncMarker().setProcessed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is likely to have changed the situation a bit
|
||||||
|
trySendPackets(onMainThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to send any remaining packets.
|
||||||
|
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||||
|
*/
|
||||||
|
public void trySendPackets(boolean onMainThread) {
|
||||||
|
|
||||||
|
// Transmit as many packets as we can
|
||||||
|
while (true) {
|
||||||
|
PacketEvent current = sendingQueue.peek();
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
AsyncMarker marker = current.getAsyncMarker();
|
||||||
|
|
||||||
|
// Abort if we're not on the main thread
|
||||||
|
if (synchronizeMain) {
|
||||||
|
try {
|
||||||
|
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||||
|
boolean wantSync = !wantAsync;
|
||||||
|
|
||||||
|
// Quit if we haven't fulfilled our promise
|
||||||
|
if ((onMainThread && wantAsync) || (!onMainThread && wantSync))
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (FieldAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (marker.isProcessed() || marker.hasExpired()) {
|
||||||
|
if (marker.isProcessed() && !current.isCancelled()) {
|
||||||
|
sendPacket(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendingQueue.poll();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only repeat when packets are removed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send every packet, regardless of the processing state.
|
||||||
|
*/
|
||||||
|
private void forceSend() {
|
||||||
|
while (true) {
|
||||||
|
PacketEvent current = sendingQueue.poll();
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
sendPacket(current);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the packet transmission must synchronize with the main thread.
|
||||||
|
* @return TRUE if it must, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isSynchronizeMain() {
|
||||||
|
return synchronizeMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit a packet, if it hasn't already.
|
||||||
|
* @param event - the packet to transmit.
|
||||||
|
*/
|
||||||
|
private void sendPacket(PacketEvent event) {
|
||||||
|
|
||||||
|
AsyncMarker marker = event.getAsyncMarker();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Don't send a packet twice
|
||||||
|
if (marker != null && !marker.isTransmitted()) {
|
||||||
|
marker.sendPacket(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Just print the error
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically transmits every delayed packet.
|
||||||
|
*/
|
||||||
|
public void cleanupAll() {
|
||||||
|
// Note that the cleanup itself will always occur on the main thread
|
||||||
|
forceSend();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
package com.comphenix.protocol.concurrency;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||||
|
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread-safe implementation of a listener multimap.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public abstract class AbstractConcurrentListenerMultimap<TListener> {
|
||||||
|
|
||||||
|
// The core of our map
|
||||||
|
private ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>> listeners =
|
||||||
|
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener<TListener>>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener to its requested list of packet recievers.
|
||||||
|
* @param listener - listener with a list of packets to recieve notifcations for.
|
||||||
|
* @param whitelist - the packet whitelist to use.
|
||||||
|
*/
|
||||||
|
public void addListener(TListener listener, ListeningWhitelist whitelist) {
|
||||||
|
|
||||||
|
PrioritizedListener<TListener> prioritized = new PrioritizedListener<TListener>(listener, whitelist.getPriority());
|
||||||
|
|
||||||
|
for (Integer packetID : whitelist.getWhitelist()) {
|
||||||
|
addListener(packetID, prioritized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the listener to a specific packet notifcation list
|
||||||
|
private void addListener(Integer packetID, PrioritizedListener<TListener> listener) {
|
||||||
|
|
||||||
|
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
|
||||||
|
|
||||||
|
// We don't want to create this for every lookup
|
||||||
|
if (list == null) {
|
||||||
|
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||||
|
// which is a essential feature for our purposes.
|
||||||
|
final SortedCopyOnWriteArray<PrioritizedListener<TListener>> value = new SortedCopyOnWriteArray<PrioritizedListener<TListener>>();
|
||||||
|
|
||||||
|
list = listeners.putIfAbsent(packetID, value);
|
||||||
|
|
||||||
|
// We may end up creating multiple multisets, but we'll agree
|
||||||
|
// on the one to use.
|
||||||
|
if (list == null) {
|
||||||
|
list = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread safe
|
||||||
|
list.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given listener from the packet event list.
|
||||||
|
* @param listener - listener to remove.
|
||||||
|
* @param whitelist - the packet whitelist that was used.
|
||||||
|
* @return Every packet ID that was removed due to no listeners.
|
||||||
|
*/
|
||||||
|
public List<Integer> removeListener(TListener listener, ListeningWhitelist whitelist) {
|
||||||
|
|
||||||
|
List<Integer> removedPackets = new ArrayList<Integer>();
|
||||||
|
|
||||||
|
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||||
|
for (Integer packetID : whitelist.getWhitelist()) {
|
||||||
|
|
||||||
|
SortedCopyOnWriteArray<PrioritizedListener<TListener>> list = listeners.get(packetID);
|
||||||
|
|
||||||
|
// Remove any listeners
|
||||||
|
if (list != null) {
|
||||||
|
// Don't remove from newly created lists
|
||||||
|
if (list.size() > 0) {
|
||||||
|
// Remove this listener. Note that priority is generally ignored.
|
||||||
|
list.remove(new PrioritizedListener<TListener>(listener, whitelist.getPriority()));
|
||||||
|
|
||||||
|
if (list.size() == 0) {
|
||||||
|
listeners.remove(packetID);
|
||||||
|
removedPackets.add(packetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move on to the next
|
||||||
|
}
|
||||||
|
|
||||||
|
return removedPackets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||||
|
* <p>
|
||||||
|
* The returned list is thread-safe and doesn't require synchronization.
|
||||||
|
* @param packetID - packet ID.
|
||||||
|
* @return Registered listeners.
|
||||||
|
*/
|
||||||
|
public Collection<PrioritizedListener<TListener>> getListener(int packetID) {
|
||||||
|
return listeners.get(packetID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every listener.
|
||||||
|
* @return Every listener.
|
||||||
|
*/
|
||||||
|
public Iterable<PrioritizedListener<TListener>> values() {
|
||||||
|
return Iterables.concat(listeners.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve every registered packet ID:
|
||||||
|
* @return Registered packet ID.
|
||||||
|
*/
|
||||||
|
public Set<Integer> keySet() {
|
||||||
|
return listeners.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all packet listeners.
|
||||||
|
*/
|
||||||
|
protected void clearListeners() {
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,7 @@ public class ListeningWhitelist {
|
|||||||
* @return TRUE if there are any packets, FALSE otherwise.
|
* @return TRUE if there are any packets, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return whitelist != null || whitelist.size() > 0;
|
return whitelist != null && whitelist.size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +86,23 @@ public class ListeningWhitelist {
|
|||||||
return Objects.hashCode(priority, whitelist);
|
return Objects.hashCode(priority, whitelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if any of the given IDs can be found in the whitelist.
|
||||||
|
* @param whitelist - whitelist to test.
|
||||||
|
* @param idList - list of packet IDs to find.
|
||||||
|
* @return TRUE if any of the packets in the list can be found in the whitelist, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean containsAny(ListeningWhitelist whitelist, int... idList) {
|
||||||
|
if (whitelist != null) {
|
||||||
|
for (int i = 0; i < idList.length; i++) {
|
||||||
|
if (whitelist.getWhitelist().contains(idList[i]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object obj){
|
public boolean equals(final Object obj){
|
||||||
if(obj instanceof ListeningWhitelist){
|
if(obj instanceof ListeningWhitelist){
|
||||||
|
@ -17,6 +17,12 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.events;
|
package com.comphenix.protocol.events;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
@ -40,13 +46,18 @@ import net.minecraft.server.Packet;
|
|||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class PacketContainer {
|
public class PacketContainer implements Serializable {
|
||||||
|
|
||||||
protected Packet handle;
|
/**
|
||||||
protected int id;
|
* Generated by Eclipse.
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 2074805748222377230L;
|
||||||
|
|
||||||
|
protected int id;
|
||||||
|
protected transient Packet handle;
|
||||||
|
|
||||||
// Current structure modifier
|
// Current structure modifier
|
||||||
protected StructureModifier<Object> structureModifier;
|
protected transient StructureModifier<Object> structureModifier;
|
||||||
|
|
||||||
// Check whether or not certain classes exists
|
// Check whether or not certain classes exists
|
||||||
private static boolean hasWorldType = false;
|
private static boolean hasWorldType = false;
|
||||||
@ -54,6 +65,10 @@ public class PacketContainer {
|
|||||||
// The getEntity method
|
// The getEntity method
|
||||||
private static Method getEntity;
|
private static Method getEntity;
|
||||||
|
|
||||||
|
// Support for serialization
|
||||||
|
private static Method writeMethod;
|
||||||
|
private static Method readMethod;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
Class.forName("net.minecraft.server.WorldType");
|
Class.forName("net.minecraft.server.WorldType");
|
||||||
@ -291,4 +306,60 @@ public class PacketContainer {
|
|||||||
public int getID() {
|
public int getID() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||||
|
// Default serialization
|
||||||
|
output.defaultWriteObject();
|
||||||
|
|
||||||
|
// We'll take care of NULL packets as well
|
||||||
|
output.writeBoolean(handle != null);
|
||||||
|
|
||||||
|
// Retrieve the write method by reflection
|
||||||
|
if (writeMethod == null)
|
||||||
|
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call the write-method
|
||||||
|
writeMethod.invoke(handle, new DataOutputStream(output));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Insufficient security privileges.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new IOException("Could not serialize Minecraft packet.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||||
|
// Default deserialization
|
||||||
|
input.defaultReadObject();
|
||||||
|
|
||||||
|
// Get structure modifier
|
||||||
|
structureModifier = StructureCache.getStructure(id);
|
||||||
|
|
||||||
|
// Don't read NULL packets
|
||||||
|
if (input.readBoolean()) {
|
||||||
|
|
||||||
|
// Create a default instance of the packet
|
||||||
|
handle = StructureCache.newPacket(id);
|
||||||
|
|
||||||
|
// Retrieve the read method by reflection
|
||||||
|
if (readMethod == null)
|
||||||
|
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
|
||||||
|
|
||||||
|
// Call the read method
|
||||||
|
try {
|
||||||
|
readMethod.invoke(handle, new DataInputStream(input));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IOException("Minecraft packet doesn't support DataInputStream", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Insufficient security privileges.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new IOException("Could not deserialize Minecraft packet.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And we're done
|
||||||
|
structureModifier = structureModifier.withTarget(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,22 +17,30 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.events;
|
package com.comphenix.protocol.events;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
import java.util.EventObject;
|
import java.util.EventObject;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.async.AsyncMarker;
|
||||||
|
|
||||||
public class PacketEvent extends EventObject implements Cancellable {
|
public class PacketEvent extends EventObject implements Cancellable {
|
||||||
/**
|
/**
|
||||||
* Automatically generated by Eclipse.
|
* Automatically generated by Eclipse.
|
||||||
*/
|
*/
|
||||||
private static final long serialVersionUID = -5360289379097430620L;
|
private static final long serialVersionUID = -5360289379097430620L;
|
||||||
|
|
||||||
|
private transient Player player;
|
||||||
private PacketContainer packet;
|
private PacketContainer packet;
|
||||||
private Player player;
|
|
||||||
private boolean serverPacket;
|
private boolean serverPacket;
|
||||||
private boolean cancel;
|
private boolean cancel;
|
||||||
|
|
||||||
|
private AsyncMarker asyncMarker;
|
||||||
|
private boolean asynchronous;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the static constructors to create instances of this event.
|
* Use the static constructors to create instances of this event.
|
||||||
* @param source - the event source.
|
* @param source - the event source.
|
||||||
@ -47,6 +55,16 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
this.player = player;
|
this.player = player;
|
||||||
this.serverPacket = serverPacket;
|
this.serverPacket = serverPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
||||||
|
super(origial.source);
|
||||||
|
this.packet = origial.packet;
|
||||||
|
this.player = origial.player;
|
||||||
|
this.cancel = origial.cancel;
|
||||||
|
this.serverPacket = origial.serverPacket;
|
||||||
|
this.asyncMarker = asyncMarker;
|
||||||
|
this.asynchronous = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an event representing a client packet transmission.
|
* Creates an event representing a client packet transmission.
|
||||||
@ -70,6 +88,16 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
return new PacketEvent(source, packet, recipient, true);
|
return new PacketEvent(source, packet, recipient, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an asynchronous packet event from a synchronous event and a async marker.
|
||||||
|
* @param event - the original synchronous event.
|
||||||
|
* @param marker - the asynchronous marker.
|
||||||
|
* @return The new packet event.
|
||||||
|
*/
|
||||||
|
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
|
||||||
|
return new PacketEvent(event, marker);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the packet that will be sent to the player.
|
* Retrieves the packet that will be sent to the player.
|
||||||
* @return Packet to send to the player.
|
* @return Packet to send to the player.
|
||||||
@ -120,9 +148,67 @@ public class PacketEvent extends EventObject implements Cancellable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not this packet was created by the server.
|
* Whether or not this packet was created by the server.
|
||||||
|
* <p>
|
||||||
|
* Most listeners can deduce this by noting which listener method was invoked.
|
||||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||||
*/
|
*/
|
||||||
public boolean isServerPacket() {
|
public boolean isServerPacket() {
|
||||||
return serverPacket;
|
return serverPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the asynchronous marker.
|
||||||
|
* <p>
|
||||||
|
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
|
||||||
|
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
|
||||||
|
* <p>
|
||||||
|
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
|
||||||
|
* @return The current asynchronous marker, or NULL.
|
||||||
|
*/
|
||||||
|
public AsyncMarker getAsyncMarker() {
|
||||||
|
return asyncMarker;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set the asynchronous marker.
|
||||||
|
* <p>
|
||||||
|
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled
|
||||||
|
* to be processed asynchronously with the given settings.
|
||||||
|
* <p>
|
||||||
|
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
|
||||||
|
* @param asyncMarker - the new asynchronous marker, or NULL.
|
||||||
|
* @throws IllegalStateException If the current event is asynchronous.
|
||||||
|
*/
|
||||||
|
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||||
|
if (isAsynchronous())
|
||||||
|
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||||
|
this.asyncMarker = asyncMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the packet event has been executed asynchronously or not.
|
||||||
|
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isAsynchronous() {
|
||||||
|
return asynchronous;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||||
|
// Default serialization
|
||||||
|
output.defaultWriteObject();
|
||||||
|
|
||||||
|
// Write the name of the player (or NULL if it's not set)
|
||||||
|
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||||
|
// Default deserialization
|
||||||
|
input.defaultReadObject();
|
||||||
|
|
||||||
|
final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject();
|
||||||
|
|
||||||
|
if (offlinePlayer != null) {
|
||||||
|
// Better than nothing
|
||||||
|
player = offlinePlayer.getPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,216 @@
|
|||||||
|
package com.comphenix.protocol.events;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a player object that can be serialized by Java.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated by Eclipse.
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = -2728976288470282810L;
|
||||||
|
|
||||||
|
private transient Location bedSpawnLocation;
|
||||||
|
|
||||||
|
// Relevant data about an offline player
|
||||||
|
private String name;
|
||||||
|
private long firstPlayed;
|
||||||
|
private long lastPlayed;
|
||||||
|
private boolean operator;
|
||||||
|
private boolean banned;
|
||||||
|
private boolean playedBefore;
|
||||||
|
private boolean online;
|
||||||
|
private boolean whitelisted;
|
||||||
|
|
||||||
|
// Proxy helper
|
||||||
|
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor used by serialization.
|
||||||
|
*/
|
||||||
|
public SerializedOfflinePlayer() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize this serializable offline player from another player.
|
||||||
|
* @param offline - another player.
|
||||||
|
*/
|
||||||
|
public SerializedOfflinePlayer(OfflinePlayer offline) {
|
||||||
|
this.name = offline.getName();
|
||||||
|
this.firstPlayed = offline.getFirstPlayed();
|
||||||
|
this.lastPlayed = offline.getLastPlayed();
|
||||||
|
this.operator = offline.isOp();
|
||||||
|
this.banned = offline.isBanned();
|
||||||
|
this.playedBefore = offline.hasPlayedBefore();
|
||||||
|
this.online = offline.isOnline();
|
||||||
|
this.whitelisted = offline.isWhitelisted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOp() {
|
||||||
|
return operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOp(boolean operator) {
|
||||||
|
this.operator = operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> serialize() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location getBedSpawnLocation() {
|
||||||
|
return bedSpawnLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFirstPlayed() {
|
||||||
|
return firstPlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLastPlayed() {
|
||||||
|
return lastPlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPlayedBefore() {
|
||||||
|
return playedBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBanned() {
|
||||||
|
return banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBanned(boolean banned) {
|
||||||
|
this.banned = banned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnline() {
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWhitelisted() {
|
||||||
|
return whitelisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWhitelisted(boolean whitelisted) {
|
||||||
|
this.whitelisted = whitelisted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||||
|
output.defaultWriteObject();
|
||||||
|
|
||||||
|
// Serialize the bed spawn location
|
||||||
|
output.writeUTF(bedSpawnLocation.getWorld().getName());
|
||||||
|
output.writeDouble(bedSpawnLocation.getX());
|
||||||
|
output.writeDouble(bedSpawnLocation.getY());
|
||||||
|
output.writeDouble(bedSpawnLocation.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||||
|
input.defaultReadObject();
|
||||||
|
|
||||||
|
// Well, this is a problem
|
||||||
|
bedSpawnLocation = new Location(
|
||||||
|
getWorld(input.readUTF()),
|
||||||
|
input.readDouble(),
|
||||||
|
input.readDouble(),
|
||||||
|
input.readDouble()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private World getWorld(String name) {
|
||||||
|
try {
|
||||||
|
// Try to get the world at least
|
||||||
|
return Bukkit.getServer().getWorld(name);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Screw it
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getPlayer() {
|
||||||
|
try {
|
||||||
|
// Try to get the real player underneath
|
||||||
|
return Bukkit.getServer().getPlayerExact(name);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return getProxyPlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a player object that implements OfflinePlayer by refering to this object.
|
||||||
|
* <p>
|
||||||
|
* All other methods cause an exception.
|
||||||
|
* @return Proxy object.
|
||||||
|
*/
|
||||||
|
public Player getProxyPlayer() {
|
||||||
|
|
||||||
|
// Remember to initialize the method filter
|
||||||
|
if (lookup.size() == 0) {
|
||||||
|
// Add all public methods
|
||||||
|
for (Method method : OfflinePlayer.class.getMethods()) {
|
||||||
|
lookup.put(method.getName(), method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MORE CGLIB magic!
|
||||||
|
Enhancer ex = new Enhancer();
|
||||||
|
ex.setSuperclass(Player.class);
|
||||||
|
ex.setCallback(new MethodInterceptor() {
|
||||||
|
@Override
|
||||||
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
|
||||||
|
// There's no overloaded methods, so we don't care
|
||||||
|
Method offlineMethod = lookup.get(method.getName());
|
||||||
|
|
||||||
|
// Ignore all other methods
|
||||||
|
if (offlineMethod == null) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"The method " + method.getName() + " is not supported for offline players.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke our on method
|
||||||
|
return offlineMethod.invoke(SerializedOfflinePlayer.this, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (Player) ex.create();
|
||||||
|
}
|
||||||
|
}
|
@ -1,194 +0,0 @@
|
|||||||
package com.comphenix.protocol.injector;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import com.comphenix.protocol.concurrency.SortedCopyOnWriteArray;
|
|
||||||
import com.comphenix.protocol.events.ListenerPriority;
|
|
||||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
|
||||||
import com.comphenix.protocol.events.PacketAdapter;
|
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A thread-safe implementation of a listener multimap.
|
|
||||||
*
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
|
||||||
public class ConcurrentListenerMultimap {
|
|
||||||
|
|
||||||
// The core of our map
|
|
||||||
protected ConcurrentMap<Integer, SortedCopyOnWriteArray<PrioritizedListener>> listeners =
|
|
||||||
new ConcurrentHashMap<Integer, SortedCopyOnWriteArray<PrioritizedListener>>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener to its requested list of packet recievers.
|
|
||||||
* @param listener - listener with a list of packets to recieve notifcations for.
|
|
||||||
* @param whitelist - the packet whitelist to use.
|
|
||||||
*/
|
|
||||||
public void addListener(PacketListener listener, ListeningWhitelist whitelist) {
|
|
||||||
|
|
||||||
PrioritizedListener prioritized = new PrioritizedListener(listener, whitelist.getPriority());
|
|
||||||
|
|
||||||
for (Integer packetID : whitelist.getWhitelist()) {
|
|
||||||
addListener(packetID, prioritized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the listener to a specific packet notifcation list
|
|
||||||
private void addListener(Integer packetID, PrioritizedListener listener) {
|
|
||||||
|
|
||||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(packetID);
|
|
||||||
|
|
||||||
// We don't want to create this for every lookup
|
|
||||||
if (list == null) {
|
|
||||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
|
||||||
// which is a essential feature for our purposes.
|
|
||||||
final SortedCopyOnWriteArray<PrioritizedListener> value = new SortedCopyOnWriteArray<PrioritizedListener>();
|
|
||||||
|
|
||||||
list = listeners.putIfAbsent(packetID, value);
|
|
||||||
|
|
||||||
// We may end up creating multiple multisets, but we'll agree
|
|
||||||
// on the one to use.
|
|
||||||
if (list == null) {
|
|
||||||
list = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thread safe
|
|
||||||
list.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the given listener from the packet event list.
|
|
||||||
* @param listener - listener to remove.
|
|
||||||
* @param whitelist - the packet whitelist that was used.
|
|
||||||
* @return Every packet ID that was removed due to no listeners.
|
|
||||||
*/
|
|
||||||
public List<Integer> removeListener(PacketListener listener, ListeningWhitelist whitelist) {
|
|
||||||
|
|
||||||
List<Integer> removedPackets = new ArrayList<Integer>();
|
|
||||||
|
|
||||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
|
||||||
for (Integer packetID : whitelist.getWhitelist()) {
|
|
||||||
|
|
||||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(packetID);
|
|
||||||
|
|
||||||
// Remove any listeners
|
|
||||||
if (list != null) {
|
|
||||||
// Don't remove from newly created lists
|
|
||||||
if (list.size() > 0) {
|
|
||||||
// Remove this listener. Note that priority is generally ignored.
|
|
||||||
list.remove(new PrioritizedListener(listener, whitelist.getPriority()));
|
|
||||||
|
|
||||||
if (list.size() == 0) {
|
|
||||||
listeners.remove(packetID);
|
|
||||||
removedPackets.add(packetID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move on to the next
|
|
||||||
}
|
|
||||||
|
|
||||||
return removedPackets;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the given packet event for every registered listener.
|
|
||||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
|
||||||
* @param event - the packet event to invoke.
|
|
||||||
*/
|
|
||||||
public void invokePacketRecieving(Logger logger, PacketEvent event) {
|
|
||||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(event.getPacketID());
|
|
||||||
|
|
||||||
if (list == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// We have to be careful. Cannot modify the underlying list when sending notifications.
|
|
||||||
synchronized (list) {
|
|
||||||
for (PrioritizedListener element : list) {
|
|
||||||
try {
|
|
||||||
element.getListener().onPacketReceiving(event);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
// Minecraft doesn't want your Exception.
|
|
||||||
logger.log(Level.SEVERE,
|
|
||||||
"Exception occured in onPacketReceiving() for " +
|
|
||||||
PacketAdapter.getPluginName(element.getListener()), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the given packet event for every registered listener.
|
|
||||||
* @param logger - the logger that will be used to inform about listener exceptions.
|
|
||||||
* @param event - the packet event to invoke.
|
|
||||||
*/
|
|
||||||
public void invokePacketSending(Logger logger, PacketEvent event) {
|
|
||||||
SortedCopyOnWriteArray<PrioritizedListener> list = listeners.get(event.getPacketID());
|
|
||||||
|
|
||||||
if (list == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
synchronized (list) {
|
|
||||||
for (PrioritizedListener element : list) {
|
|
||||||
try {
|
|
||||||
element.getListener().onPacketSending(event);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
// Minecraft doesn't want your Exception.
|
|
||||||
logger.log(Level.SEVERE,
|
|
||||||
"Exception occured in onPacketReceiving() for " +
|
|
||||||
PacketAdapter.getPluginName(element.getListener()), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener with an associated priority.
|
|
||||||
*/
|
|
||||||
private class PrioritizedListener implements Comparable<PrioritizedListener> {
|
|
||||||
private PacketListener listener;
|
|
||||||
private ListenerPriority priority;
|
|
||||||
|
|
||||||
public PrioritizedListener(PacketListener listener, ListenerPriority priority) {
|
|
||||||
this.listener = listener;
|
|
||||||
this.priority = priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(PrioritizedListener other) {
|
|
||||||
// This ensures that lower priority listeners are executed first
|
|
||||||
return Ints.compare(this.getPriority().getSlot(),
|
|
||||||
other.getPriority().getSlot());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that this equals() method is NOT consistent with compareTo().
|
|
||||||
// But, it's a private class so who cares.
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
// We only care about the listener - priority itself should not make a difference
|
|
||||||
if(obj instanceof PrioritizedListener){
|
|
||||||
final PrioritizedListener other = (PrioritizedListener) obj;
|
|
||||||
return Objects.equal(listener, other.listener);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PacketListener getListener() {
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenerPriority getPriority() {
|
|
||||||
return priority;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,8 +35,39 @@ class EntityUtilities {
|
|||||||
private static Method hashGetMethod;
|
private static Method hashGetMethod;
|
||||||
private static Method scanPlayersMethod;
|
private static Method scanPlayersMethod;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While this function may look pretty bad, it's essentially just a reflection-warped
|
||||||
|
* version of the following:
|
||||||
|
*
|
||||||
|
* @SuppressWarnings("unchecked")
|
||||||
|
* public static void updateEntity2(Entity entity, List<Player> observers) {
|
||||||
|
*
|
||||||
|
* World world = entity.getWorld();
|
||||||
|
* WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||||
|
*
|
||||||
|
* EntityTracker tracker = worldServer.tracker;
|
||||||
|
* EntityTrackerEntry entry = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||||
|
*
|
||||||
|
* List<EntityPlayer> nmsPlayers = getNmsPlayers(observers);
|
||||||
|
*
|
||||||
|
* entry.trackedPlayers.removeAll(nmsPlayers);
|
||||||
|
* entry.scanPlayers(nmsPlayers);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* private static List<EntityPlayer> getNmsPlayers(List<Player> players) {
|
||||||
|
* List<EntityPlayer> nsmPlayers = new ArrayList<EntityPlayer>();
|
||||||
|
*
|
||||||
|
* for (Player bukkitPlayer : players) {
|
||||||
|
* CraftPlayer craftPlayer = (CraftPlayer) bukkitPlayer;
|
||||||
|
* nsmPlayers.add(craftPlayer.getHandle());
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return nsmPlayers;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||||
|
|
||||||
World world = entity.getWorld();
|
World world = entity.getWorld();
|
||||||
Object worldServer = ((CraftWorld) world).getHandle();
|
Object worldServer = ((CraftWorld) world).getHandle();
|
||||||
|
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.VolatileField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to ensure that the 1.3 server is referencing the correct server handler.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class InjectedServerConnection {
|
||||||
|
|
||||||
|
private static Field listenerThreadField;
|
||||||
|
private static Field minecraftServerField;
|
||||||
|
private static Method serverConnectionMethod;
|
||||||
|
private static Field listField;
|
||||||
|
|
||||||
|
private List<VolatileField> listFields;
|
||||||
|
private List<ReplacedArrayList<Object>> replacedLists;
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private Logger logger;
|
||||||
|
private boolean hasAttempted;
|
||||||
|
private boolean hasSuccess;
|
||||||
|
|
||||||
|
private Object minecraftServer = null;
|
||||||
|
|
||||||
|
public InjectedServerConnection(Logger logger, Server server) {
|
||||||
|
this.listFields = new ArrayList<VolatileField>();
|
||||||
|
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||||
|
this.logger = logger;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void injectList() {
|
||||||
|
|
||||||
|
// Only execute this method once
|
||||||
|
if (!hasAttempted)
|
||||||
|
hasAttempted = true;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (minecraftServerField == null)
|
||||||
|
minecraftServerField = FuzzyReflection.fromObject(server, true).getFieldByType(".*MinecraftServer");
|
||||||
|
|
||||||
|
try {
|
||||||
|
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||||
|
} catch (IllegalAccessException e1) {
|
||||||
|
logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (serverConnectionMethod == null)
|
||||||
|
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||||
|
getMethodByParameters("getServerConnection", ".*ServerConnection", new String[] {});
|
||||||
|
// We're using Minecraft 1.3.1
|
||||||
|
injectServerConnection();
|
||||||
|
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
|
||||||
|
// Minecraft 1.2.5 or lower
|
||||||
|
injectListenerThread();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectListenerThread() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (listenerThreadField == null)
|
||||||
|
listenerThreadField = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||||
|
getFieldByType(".*NetworkListenThread");
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object listenerThread = null;
|
||||||
|
|
||||||
|
// Attempt to get the thread
|
||||||
|
try {
|
||||||
|
listenerThread = listenerThreadField.get(minecraftServer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.WARNING, "Unable to read the listener thread.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, great. Get every list field
|
||||||
|
List<Field> lists = FuzzyReflection.fromClass(listenerThreadField.getType()).getFieldListByType(List.class);
|
||||||
|
|
||||||
|
for (Field list : lists) {
|
||||||
|
injectIntoList(listenerThread, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void injectServerConnection() {
|
||||||
|
|
||||||
|
Object serverConnection = null;
|
||||||
|
|
||||||
|
// Careful - we might fail
|
||||||
|
try {
|
||||||
|
serverConnection = serverConnectionMethod.invoke(minecraftServer);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
logger.log(Level.WARNING, "Unable to retrieve server connection", ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listField == null)
|
||||||
|
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
||||||
|
getFieldByType("serverConnection", List.class);
|
||||||
|
injectIntoList(serverConnection, listField);
|
||||||
|
hasSuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void injectIntoList(Object instance, Field field) {
|
||||||
|
VolatileField listFieldRef = new VolatileField(listField, instance, true);
|
||||||
|
List<Object> list = (List<Object>) listFieldRef.getValue();
|
||||||
|
|
||||||
|
// Careful not to inject twice
|
||||||
|
if (list instanceof ReplacedArrayList) {
|
||||||
|
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||||
|
} else {
|
||||||
|
replacedLists.add(new ReplacedArrayList<Object>(list));
|
||||||
|
listFieldRef.setValue(replacedLists.get(0));
|
||||||
|
listFields.add(listFieldRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the server handler instance kept by the "keep alive" object.
|
||||||
|
* @param oldHandler - old server handler.
|
||||||
|
* @param newHandler - new, proxied server handler.
|
||||||
|
*/
|
||||||
|
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
||||||
|
if (!hasAttempted) {
|
||||||
|
injectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSuccess) {
|
||||||
|
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||||
|
replacedList.addMapping(oldHandler, newHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert to the old vanilla server handler, if it has been replaced.
|
||||||
|
* @param oldHandler - old vanilla server handler.
|
||||||
|
*/
|
||||||
|
public void revertServerHandler(Object oldHandler) {
|
||||||
|
if (hasSuccess) {
|
||||||
|
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||||
|
replacedList.removeMapping(oldHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undoes everything.
|
||||||
|
*/
|
||||||
|
public void cleanupAll() {
|
||||||
|
if (replacedLists.size() > 0) {
|
||||||
|
// Repair the underlying lists
|
||||||
|
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||||
|
replacedList.revertAll();
|
||||||
|
}
|
||||||
|
for (VolatileField field : listFields) {
|
||||||
|
field.revertValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
listFields.clear();
|
||||||
|
replacedLists.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ class MinecraftRegistry {
|
|||||||
// Initialize it, if we haven't already
|
// Initialize it, if we haven't already
|
||||||
if (packetToID == null) {
|
if (packetToID == null) {
|
||||||
try {
|
try {
|
||||||
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("java\\.util\\.Map");
|
Field packetsField = FuzzyReflection.fromClass(Packet.class, true).getFieldByType("packetsField", Map.class);
|
||||||
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
packetToID = (Map<Class, Integer>) FieldUtils.readStaticField(packetsField, true);
|
||||||
|
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
@ -88,8 +88,8 @@ class MinecraftRegistry {
|
|||||||
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
|
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
|
||||||
|
|
||||||
// Optimized lookup
|
// Optimized lookup
|
||||||
if (lookup.containsKey(packetToID)) {
|
if (lookup.containsKey(packetID)) {
|
||||||
return lookup.get(packetToID);
|
return lookup.get(packetID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Will most likely not be used
|
// Will most likely not be used
|
||||||
|
@ -10,6 +10,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
@ -48,7 +51,7 @@ class NetworkFieldInjector extends PlayerInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() throws IllegalAccessException {
|
protected synchronized void initialize() throws IllegalAccessException {
|
||||||
super.initialize();
|
super.initialize();
|
||||||
|
|
||||||
// Get the sync field as well
|
// Get the sync field as well
|
||||||
@ -83,6 +86,15 @@ class NetworkFieldInjector extends PlayerInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkListener(PacketListener listener) {
|
||||||
|
// Unfortunately, we don't support chunk packets
|
||||||
|
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
|
||||||
|
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
|
||||||
|
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectManager() {
|
public void injectManager() {
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.Packets;
|
||||||
|
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
||||||
*
|
*
|
||||||
@ -42,6 +46,15 @@ class NetworkObjectInjector extends PlayerInjector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkListener(PacketListener listener) {
|
||||||
|
// Unfortunately, we don't support chunk packets
|
||||||
|
if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(),
|
||||||
|
Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) {
|
||||||
|
throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectManager() {
|
public void injectManager() {
|
||||||
|
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import net.minecraft.server.Packet;
|
||||||
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
|
import net.sf.cglib.proxy.Factory;
|
||||||
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||||
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
|
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
|
||||||
|
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a player hook into the NetServerHandler class.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class NetworkServerInjector extends PlayerInjector {
|
||||||
|
|
||||||
|
private static Method sendPacketMethod;
|
||||||
|
|
||||||
|
private StructureModifier<Object> serverHandlerModifier;
|
||||||
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
|
public NetworkServerInjector(Player player, PacketFilterManager manager,
|
||||||
|
Set<Integer> sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException {
|
||||||
|
super(player, manager, sendingFilters);
|
||||||
|
this.serverInjection = serverInjection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialize() throws IllegalAccessException {
|
||||||
|
super.initialize();
|
||||||
|
|
||||||
|
// Get the send packet method!
|
||||||
|
if (hasInitialized) {
|
||||||
|
if (sendPacketMethod == null)
|
||||||
|
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
|
||||||
|
if (serverHandlerModifier == null)
|
||||||
|
serverHandlerModifier = new StructureModifier<Object>(serverHandler.getClass(), null, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Packet packet, boolean filtered) throws InvocationTargetException {
|
||||||
|
Object serverDeleage = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue();
|
||||||
|
|
||||||
|
if (serverDeleage != null) {
|
||||||
|
try {
|
||||||
|
// Note that invocation target exception is a wrapper for a checked exception
|
||||||
|
sendPacketMethod.invoke(serverDeleage, packet);
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException("Unable to access send packet method.", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unable to load server handler. Cannot send packet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectManager() {
|
||||||
|
|
||||||
|
if (serverHandlerRef == null)
|
||||||
|
throw new IllegalStateException("Cannot find server handler.");
|
||||||
|
// Don't inject twice
|
||||||
|
if (serverHandlerRef.getValue() instanceof Factory)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Class<?> serverClass = serverHandler.getClass();
|
||||||
|
|
||||||
|
Enhancer ex = new Enhancer();
|
||||||
|
ex.setClassLoader(manager.getClassLoader());
|
||||||
|
ex.setSuperclass(serverClass);
|
||||||
|
ex.setCallback(new MethodInterceptor() {
|
||||||
|
@Override
|
||||||
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
|
||||||
|
// The send packet method!
|
||||||
|
if (method.equals(sendPacketMethod)) {
|
||||||
|
Packet packet = (Packet) args[0];
|
||||||
|
|
||||||
|
if (packet != null) {
|
||||||
|
packet = handlePacketRecieved(packet);
|
||||||
|
|
||||||
|
// A NULL packet indicate cancelling
|
||||||
|
if (packet != null)
|
||||||
|
args[0] = packet;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method directly
|
||||||
|
return proxy.invokeSuper(obj, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use the existing field values when we create our copy
|
||||||
|
DefaultInstances serverInstances = DefaultInstances.fromArray(
|
||||||
|
ExistingGenerator.fromObjectFields(serverHandler),
|
||||||
|
PrimitiveGenerator.INSTANCE,
|
||||||
|
CollectionGenerator.INSTANCE);
|
||||||
|
|
||||||
|
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
|
||||||
|
serverInjection.replaceServerHandler(serverHandler, proxyObject);
|
||||||
|
|
||||||
|
// Inject it now
|
||||||
|
if (proxyObject != null) {
|
||||||
|
copyTo(serverHandler, proxyObject);
|
||||||
|
serverHandlerRef.setValue(proxyObject);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy every field in server handler A to server handler B.
|
||||||
|
* @param source - fields to copy.
|
||||||
|
* @param destination - fields to copy to.
|
||||||
|
*/
|
||||||
|
private void copyTo(Object source, Object destination) {
|
||||||
|
StructureModifier<Object> modifierSource = serverHandlerModifier.withTarget(source);
|
||||||
|
StructureModifier<Object> modifierDest = serverHandlerModifier.withTarget(destination);
|
||||||
|
|
||||||
|
// Copy every field
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < modifierSource.size(); i++) {
|
||||||
|
modifierDest.write(i, modifierSource.read(i));
|
||||||
|
}
|
||||||
|
} catch (FieldAccessException e) {
|
||||||
|
throw new RuntimeException("Unable to copy fields from NetServerHandler.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanupAll() {
|
||||||
|
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
|
||||||
|
copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue());
|
||||||
|
serverHandlerRef.revertValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
serverInjection.revertServerHandler(serverHandler);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (getNetHandler() != null) {
|
||||||
|
// Restore packet listener
|
||||||
|
try {
|
||||||
|
FieldUtils.writeField(netHandlerField, networkManager, serverHandlerRef.getOldValue(), true);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// Oh well
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkListener(PacketListener listener) {
|
||||||
|
// We support everything
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer;
|
|||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
import net.sf.cglib.proxy.MethodProxy;
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
|
|
||||||
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
@ -45,7 +46,10 @@ import org.bukkit.event.server.PluginDisableEvent;
|
|||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.AsynchronousManager;
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||||
|
import com.comphenix.protocol.async.AsyncMarker;
|
||||||
import com.comphenix.protocol.events.*;
|
import com.comphenix.protocol.events.*;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
@ -60,14 +64,23 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
*/
|
*/
|
||||||
public enum PlayerInjectHooks {
|
public enum PlayerInjectHooks {
|
||||||
/**
|
/**
|
||||||
* Override the packet queue lists in NetworkHandler.
|
* Override the packet queue lists in NetworkHandler.
|
||||||
|
* <p>
|
||||||
|
* Cannot intercept MapChunk packets.
|
||||||
*/
|
*/
|
||||||
NETWORK_HANDLER_FIELDS,
|
NETWORK_HANDLER_FIELDS,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the network handler object itself.
|
* Override the network handler object itself. Only works in 1.3.
|
||||||
|
* <p>
|
||||||
|
* Cannot intercept MapChunk packets.
|
||||||
*/
|
*/
|
||||||
NETWORK_MANAGER_OBJECT
|
NETWORK_MANAGER_OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override the server handler object. Versatile, but a tad slower.
|
||||||
|
*/
|
||||||
|
NETWORK_SERVER_OBJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a concurrent set
|
// Create a concurrent set
|
||||||
@ -84,26 +97,35 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
// Packet injection
|
// Packet injection
|
||||||
private PacketInjector packetInjector;
|
private PacketInjector packetInjector;
|
||||||
|
|
||||||
|
// Server connection injection
|
||||||
|
private InjectedServerConnection serverInjection;
|
||||||
|
|
||||||
// Enabled packet filters
|
// Enabled packet filters
|
||||||
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
||||||
|
|
||||||
// The two listener containers
|
// The two listener containers
|
||||||
private ConcurrentListenerMultimap recievedListeners = new ConcurrentListenerMultimap();
|
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
|
||||||
private ConcurrentListenerMultimap sendingListeners = new ConcurrentListenerMultimap();
|
private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
|
||||||
|
|
||||||
// Whether or not this class has been closed
|
// Whether or not this class has been closed
|
||||||
private boolean hasClosed;
|
private volatile boolean hasClosed;
|
||||||
|
|
||||||
// The default class loader
|
// The default class loader
|
||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
|
|
||||||
|
// The last successful player hook
|
||||||
|
private PlayerInjector lastSuccessfulHook;
|
||||||
|
|
||||||
// Error logger
|
// Error logger
|
||||||
private Logger logger;
|
private Logger logger;
|
||||||
|
|
||||||
|
// The async packet handler
|
||||||
|
private AsyncFilterManager asyncFilterManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only create instances of this class if protocol lib is disabled.
|
* Only create instances of this class if protocol lib is disabled.
|
||||||
*/
|
*/
|
||||||
public PacketFilterManager(ClassLoader classLoader, Logger logger) {
|
public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) {
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
throw new IllegalArgumentException("logger cannot be NULL.");
|
throw new IllegalArgumentException("logger cannot be NULL.");
|
||||||
if (classLoader == null)
|
if (classLoader == null)
|
||||||
@ -114,11 +136,18 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
|
||||||
|
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
|
||||||
|
this.serverInjection = new InjectedServerConnection(logger, server);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
|
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsynchronousManager getAsynchronousManager() {
|
||||||
|
return asyncFilterManager;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves how the server packets are read.
|
* Retrieves how the server packets are read.
|
||||||
* @return Injection method for reading server packets.
|
* @return Injection method for reading server packets.
|
||||||
@ -133,6 +162,13 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
*/
|
*/
|
||||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||||
this.playerHook = playerHook;
|
this.playerHook = playerHook;
|
||||||
|
|
||||||
|
// Make sure the current listeners are compatible
|
||||||
|
if (lastSuccessfulHook != null) {
|
||||||
|
for (PacketListener listener : packetListeners) {
|
||||||
|
checkListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Logger getLogger() {
|
public Logger getLogger() {
|
||||||
@ -161,12 +197,17 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
if (hasSending || hasReceiving) {
|
if (hasSending || hasReceiving) {
|
||||||
// Add listeners and hooks
|
// Add listeners and hooks
|
||||||
if (hasSending) {
|
if (hasSending) {
|
||||||
|
verifyWhitelist(listener, sending);
|
||||||
sendingListeners.addListener(listener, sending);
|
sendingListeners.addListener(listener, sending);
|
||||||
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
||||||
}
|
}
|
||||||
if (hasReceiving) {
|
if (hasReceiving) {
|
||||||
|
verifyWhitelist(listener, receiving);
|
||||||
recievedListeners.addListener(listener, receiving);
|
recievedListeners.addListener(listener, receiving);
|
||||||
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
||||||
|
|
||||||
|
// We don't know if we've hooked any players yet
|
||||||
|
checkListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform our injected hooks
|
// Inform our injected hooks
|
||||||
@ -174,6 +215,36 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the packet IDs in a whitelist is valid.
|
||||||
|
* @param listener - the listener that will be mentioned in the error.
|
||||||
|
* @param whitelist - whitelist of packet IDs.
|
||||||
|
* @throws IllegalArgumentException If the whitelist is illegal.
|
||||||
|
*/
|
||||||
|
public static void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
|
||||||
|
for (Integer id : whitelist.getWhitelist()) {
|
||||||
|
if (id >= 256 || id < 0) {
|
||||||
|
throw new IllegalArgumentException(String.format("Invalid packet id %s in listener %s.",
|
||||||
|
id, PacketAdapter.getPluginName(listener))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a listener is valid or not.
|
||||||
|
* @param listener - listener to check.
|
||||||
|
* @throws IllegalStateException If the given listener's whitelist cannot be fulfilled.
|
||||||
|
*/
|
||||||
|
public void checkListener(PacketListener listener) {
|
||||||
|
try {
|
||||||
|
if (lastSuccessfulHook != null)
|
||||||
|
lastSuccessfulHook.checkListener(listener);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePacketListener(PacketListener listener) {
|
public void removePacketListener(PacketListener listener) {
|
||||||
if (listener == null)
|
if (listener == null)
|
||||||
@ -212,6 +283,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
removePacketListener(listener);
|
removePacketListener(listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do the same for the asynchronous events
|
||||||
|
asyncFilterManager.unregisterAsyncHandlers(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,7 +293,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
* @param event - the packet event to invoke.
|
* @param event - the packet event to invoke.
|
||||||
*/
|
*/
|
||||||
public void invokePacketRecieving(PacketEvent event) {
|
public void invokePacketRecieving(PacketEvent event) {
|
||||||
recievedListeners.invokePacketRecieving(logger, event);
|
handlePacket(recievedListeners, event, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -227,7 +301,41 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
* @param event - the packet event to invoke.
|
* @param event - the packet event to invoke.
|
||||||
*/
|
*/
|
||||||
public void invokePacketSending(PacketEvent event) {
|
public void invokePacketSending(PacketEvent event) {
|
||||||
sendingListeners.invokePacketSending(logger, event);
|
handlePacket(sendingListeners, event, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a packet sending or receiving event.
|
||||||
|
* <p>
|
||||||
|
* Note that we also handle asynchronous events.
|
||||||
|
* @param packetListeners - packet listeners that will receive this event.
|
||||||
|
* @param event - the evnet to broadcast.
|
||||||
|
*/
|
||||||
|
private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
|
||||||
|
|
||||||
|
// By default, asynchronous packets are queued for processing
|
||||||
|
if (asyncFilterManager.hasAsynchronousListeners(event)) {
|
||||||
|
event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process synchronous events
|
||||||
|
if (sending)
|
||||||
|
packetListeners.invokePacketSending(logger, event);
|
||||||
|
else
|
||||||
|
packetListeners.invokePacketRecieving(logger, event);
|
||||||
|
|
||||||
|
// To cancel asynchronous processing, use the async marker
|
||||||
|
if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
|
||||||
|
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
|
||||||
|
|
||||||
|
// The above makes a copy of the event, so it's safe to cancel it
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NULL marker mean we're dealing with no asynchronous listeners
|
||||||
|
private boolean hasAsyncCancelled(AsyncMarker marker) {
|
||||||
|
return marker == null || marker.isAsyncCancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,6 +406,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
PlayerInjector injector = getInjector(sender);
|
PlayerInjector injector = getInjector(sender);
|
||||||
Packet mcPacket = packet.getHandle();
|
Packet mcPacket = packet.getHandle();
|
||||||
|
|
||||||
|
// Make sure the packet isn't cancelled
|
||||||
|
packetInjector.undoCancel(packet.getID(), mcPacket);
|
||||||
|
|
||||||
if (filters) {
|
if (filters) {
|
||||||
mcPacket = injector.handlePacketRecieved(mcPacket);
|
mcPacket = injector.handlePacketRecieved(mcPacket);
|
||||||
}
|
}
|
||||||
@ -343,8 +454,6 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||||
|
|
||||||
|
|
||||||
EntityUtilities.updateEntity(entity, observers);
|
EntityUtilities.updateEntity(entity, observers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,20 +469,22 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
/**
|
/**
|
||||||
* Used to construct a player hook.
|
* Used to construct a player hook.
|
||||||
* @param player - the player to hook.
|
* @param player - the player to hook.
|
||||||
|
* @param hook - the hook type.
|
||||||
* @return A new player hoook
|
* @return A new player hoook
|
||||||
* @throws IllegalAccessException Unable to do our reflection magic.
|
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||||
*/
|
*/
|
||||||
protected PlayerInjector getPlayerHookInstance(Player player) throws IllegalAccessException {
|
protected PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
|
||||||
|
|
||||||
// Construct the correct player hook
|
// Construct the correct player hook
|
||||||
switch (playerHook) {
|
switch (hook) {
|
||||||
case NETWORK_HANDLER_FIELDS:
|
case NETWORK_HANDLER_FIELDS:
|
||||||
return new NetworkFieldInjector(player, this, sendingFilters);
|
return new NetworkFieldInjector(player, this, sendingFilters);
|
||||||
case NETWORK_MANAGER_OBJECT:
|
case NETWORK_MANAGER_OBJECT:
|
||||||
return new NetworkObjectInjector(player, this, sendingFilters);
|
return new NetworkObjectInjector(player, this, sendingFilters);
|
||||||
|
case NETWORK_SERVER_OBJECT:
|
||||||
|
return new NetworkServerInjector(player, this, sendingFilters, serverInjection);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -381,20 +492,51 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
* @param player - player to hook.
|
* @param player - player to hook.
|
||||||
*/
|
*/
|
||||||
protected void injectPlayer(Player player) {
|
protected void injectPlayer(Player player) {
|
||||||
|
|
||||||
|
PlayerInjector injector = null;
|
||||||
|
PlayerInjectHooks currentHook = playerHook;
|
||||||
|
boolean firstPlayer = lastSuccessfulHook == null;
|
||||||
|
|
||||||
// Don't inject if the class has closed
|
// Don't inject if the class has closed
|
||||||
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
|
||||||
try {
|
while (true) {
|
||||||
PlayerInjector injector = getPlayerHookInstance(player);
|
try {
|
||||||
|
injector = getHookInstance(player, currentHook);
|
||||||
|
injector.injectManager();
|
||||||
|
playerInjection.put(player, injector);
|
||||||
|
connectionLookup.put(injector.getInputStream(false), player);
|
||||||
|
break;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
injector.injectManager();
|
// Mark this injection attempt as a failure
|
||||||
playerInjection.put(player, injector);
|
logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e);
|
||||||
connectionLookup.put(injector.getInputStream(false), player);
|
|
||||||
|
// Clean up as much as possible
|
||||||
} catch (IllegalAccessException e) {
|
try {
|
||||||
// Mark this injection attempt as a failure
|
if (injector != null)
|
||||||
playerInjection.put(player, null);
|
injector.cleanupAll();
|
||||||
logger.log(Level.SEVERE, "Unable to access fields.", e);
|
} catch (Exception e2) {
|
||||||
|
logger.log(Level.WARNING, "Cleaing up after player hook failed.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentHook.ordinal() > 0) {
|
||||||
|
// Choose the previous player hook type
|
||||||
|
currentHook = PlayerInjectHooks.values()[currentHook.ordinal() - 1];
|
||||||
|
logger.log(Level.INFO, "Switching to " + currentHook.toString() + " instead.");
|
||||||
|
} else {
|
||||||
|
// UTTER FAILURE
|
||||||
|
playerInjection.put(player, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update values
|
||||||
|
if (injector != null)
|
||||||
|
lastSuccessfulHook = injector;
|
||||||
|
if (currentHook != playerHook || firstPlayer)
|
||||||
|
setPlayerHook(currentHook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,7 +619,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
if (event instanceof PlayerJoinEvent)
|
if (event instanceof PlayerJoinEvent)
|
||||||
injectPlayer(((PlayerJoinEvent) event).getPlayer());
|
injectPlayer(((PlayerJoinEvent) event).getPlayer());
|
||||||
else if (event instanceof PlayerQuitEvent)
|
else if (event instanceof PlayerQuitEvent)
|
||||||
injectPlayer(((PlayerQuitEvent) event).getPlayer());
|
uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -524,12 +666,12 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
if (!hasClosed && player != null) {
|
if (!hasClosed && player != null) {
|
||||||
|
|
||||||
PlayerInjector injector = playerInjection.get(player);
|
PlayerInjector injector = playerInjection.get(player);
|
||||||
DataInputStream input = injector.getInputStream(true);
|
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
|
DataInputStream input = injector.getInputStream(true);
|
||||||
injector.cleanupAll();
|
injector.cleanupAll();
|
||||||
|
|
||||||
playerInjection.remove(injector);
|
playerInjection.remove(player);
|
||||||
connectionLookup.remove(input);
|
connectionLookup.remove(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -565,23 +707,31 @@ public final class PacketFilterManager implements ProtocolManager {
|
|||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
// Guard
|
// Guard
|
||||||
if (hasClosed)
|
if (hasClosed || playerInjection == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Remove everything
|
// Remove everything
|
||||||
for (PlayerInjector injection : playerInjection.values()) {
|
for (PlayerInjector injection : playerInjection.values()) {
|
||||||
injection.cleanupAll();
|
if (injection != null) {
|
||||||
|
injection.cleanupAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove packet handlers
|
// Remove packet handlers
|
||||||
if (packetInjector != null)
|
if (packetInjector != null)
|
||||||
packetInjector.cleanupAll();
|
packetInjector.cleanupAll();
|
||||||
|
|
||||||
|
// Remove server handler
|
||||||
|
serverInjection.cleanupAll();
|
||||||
|
hasClosed = true;
|
||||||
|
|
||||||
// Remove listeners
|
// Remove listeners
|
||||||
packetListeners.clear();
|
packetListeners.clear();
|
||||||
playerInjection.clear();
|
playerInjection.clear();
|
||||||
connectionLookup.clear();
|
connectionLookup.clear();
|
||||||
hasClosed = true;
|
|
||||||
|
// Clean up async handlers. We have to do this last.
|
||||||
|
asyncFilterManager.cleanupAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@ -52,6 +53,9 @@ class PacketInjector {
|
|||||||
// Allows us to determine the sender
|
// Allows us to determine the sender
|
||||||
private Map<DataInputStream, Player> playerLookup;
|
private Map<DataInputStream, Player> playerLookup;
|
||||||
|
|
||||||
|
// Allows us to look up read packet injectors
|
||||||
|
private Map<Integer, ReadPacketModifier> readModifier;
|
||||||
|
|
||||||
// Class loader
|
// Class loader
|
||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
|
|
||||||
@ -61,9 +65,24 @@ class PacketInjector {
|
|||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.playerLookup = playerLookup;
|
this.playerLookup = playerLookup;
|
||||||
|
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo a packet cancel.
|
||||||
|
* @param id - the id of the packet.
|
||||||
|
* @param packet - packet to uncancel.
|
||||||
|
*/
|
||||||
|
public void undoCancel(Integer id, Packet packet) {
|
||||||
|
ReadPacketModifier modifier = readModifier.get(id);
|
||||||
|
|
||||||
|
// Cancelled packets are represented with NULL
|
||||||
|
if (modifier != null && modifier.getOverride(packet) == null) {
|
||||||
|
modifier.removeOverride(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initialize() throws IllegalAccessException {
|
private void initialize() throws IllegalAccessException {
|
||||||
if (intHashMap == null) {
|
if (intHashMap == null) {
|
||||||
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
// We're looking for the first static field with a Minecraft-object. This should be a IntHashMap.
|
||||||
@ -109,10 +128,12 @@ class PacketInjector {
|
|||||||
ex.setClassLoader(classLoader);
|
ex.setClassLoader(classLoader);
|
||||||
Class proxy = ex.createClass();
|
Class proxy = ex.createClass();
|
||||||
|
|
||||||
|
// Create the proxy handler
|
||||||
|
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this);
|
||||||
|
readModifier.put(packetID, modifier);
|
||||||
|
|
||||||
// Add a static reference
|
// Add a static reference
|
||||||
Enhancer.registerStaticCallbacks(proxy, new Callback[] {
|
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
|
||||||
new ReadPacketModifier(packetID, this)
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Override values
|
// Override values
|
||||||
@ -147,6 +168,7 @@ class PacketInjector {
|
|||||||
|
|
||||||
putMethod.invoke(intHashMap, packetID, old);
|
putMethod.invoke(intHashMap, packetID, old);
|
||||||
previous.remove(packetID);
|
previous.remove(packetID);
|
||||||
|
readModifier.remove(packetID);
|
||||||
registry.remove(proxy);
|
registry.remove(proxy);
|
||||||
overwritten.remove(packetID);
|
overwritten.remove(packetID);
|
||||||
return true;
|
return true;
|
||||||
|
@ -31,6 +31,7 @@ import org.bukkit.entity.Player;
|
|||||||
|
|
||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
@ -56,9 +57,11 @@ abstract class PlayerInjector {
|
|||||||
|
|
||||||
// Reference to the player's network manager
|
// Reference to the player's network manager
|
||||||
protected VolatileField networkManagerRef;
|
protected VolatileField networkManagerRef;
|
||||||
|
protected VolatileField serverHandlerRef;
|
||||||
protected Object networkManager;
|
protected Object networkManager;
|
||||||
|
|
||||||
// Current net handler
|
// Current net handler
|
||||||
|
protected Object serverHandler;
|
||||||
protected Object netHandler;
|
protected Object netHandler;
|
||||||
|
|
||||||
// The packet manager and filters
|
// The packet manager and filters
|
||||||
@ -75,12 +78,18 @@ abstract class PlayerInjector {
|
|||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the notch (NMS) entity player object.
|
||||||
|
* @return Notch player object.
|
||||||
|
*/
|
||||||
|
protected EntityPlayer getEntityPlayer() {
|
||||||
|
CraftPlayer craft = (CraftPlayer) player;
|
||||||
|
return craft.getHandle();
|
||||||
|
}
|
||||||
|
|
||||||
protected void initialize() throws IllegalAccessException {
|
protected void initialize() throws IllegalAccessException {
|
||||||
|
|
||||||
CraftPlayer craft = (CraftPlayer) player;
|
EntityPlayer notchEntity = getEntityPlayer();
|
||||||
EntityPlayer notchEntity = craft.getHandle();
|
|
||||||
|
|
||||||
Object serverHandler = null;
|
|
||||||
|
|
||||||
if (!hasInitialized) {
|
if (!hasInitialized) {
|
||||||
// Do this first, in case we encounter an exception
|
// Do this first, in case we encounter an exception
|
||||||
@ -89,7 +98,8 @@ abstract class PlayerInjector {
|
|||||||
// Retrieve the server handler
|
// Retrieve the server handler
|
||||||
if (serverHandlerField == null)
|
if (serverHandlerField == null)
|
||||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
|
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
|
||||||
serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
|
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
|
||||||
|
serverHandler = serverHandlerRef.getValue();
|
||||||
|
|
||||||
// Next, get the network manager
|
// Next, get the network manager
|
||||||
if (networkManagerField == null)
|
if (networkManagerField == null)
|
||||||
@ -118,26 +128,31 @@ abstract class PlayerInjector {
|
|||||||
* @return Current net handler.
|
* @return Current net handler.
|
||||||
* @throws IllegalAccessException Unable to find or retrieve net handler.
|
* @throws IllegalAccessException Unable to find or retrieve net handler.
|
||||||
*/
|
*/
|
||||||
private Object getNetHandler() throws IllegalAccessException {
|
protected Object getNetHandler() throws IllegalAccessException {
|
||||||
|
|
||||||
// What a mess
|
// What a mess
|
||||||
try {
|
try {
|
||||||
if (netHandlerField == null)
|
if (netHandlerField == null)
|
||||||
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
|
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||||
getFieldByType("net\\.minecraft\\.NetHandler");
|
getFieldByType("net\\.minecraft\\.NetHandler");
|
||||||
} catch (RuntimeException e1) {
|
} catch (RuntimeException e1) {
|
||||||
|
// Swallow it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second attempt
|
||||||
|
if (netHandlerField == null) {
|
||||||
try {
|
try {
|
||||||
// Well, that sucks. Try just Minecraft objects then.
|
// Well, that sucks. Try just Minecraft objects then.
|
||||||
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
|
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
|
||||||
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
|
||||||
|
|
||||||
} catch (RuntimeException e2) {
|
} catch (RuntimeException e2) {
|
||||||
return new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the handler
|
// Get the handler
|
||||||
if (netHandler != null)
|
if (netHandler == null)
|
||||||
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
|
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
|
||||||
return netHandler;
|
return netHandler;
|
||||||
}
|
}
|
||||||
@ -190,6 +205,14 @@ abstract class PlayerInjector {
|
|||||||
*/
|
*/
|
||||||
public abstract void cleanupAll();
|
public abstract void cleanupAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked before a new listener is registered.
|
||||||
|
* <p>
|
||||||
|
* The player injector should throw an exception if this listener cannot be properly supplied with packet events.
|
||||||
|
* @param listener - the listener that is about to be registered.
|
||||||
|
*/
|
||||||
|
public abstract void checkListener(PacketListener listener);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a packet to be recieved by the listeners.
|
* Allows a packet to be recieved by the listeners.
|
||||||
* @param packet - packet to recieve.
|
* @param packet - packet to recieve.
|
||||||
@ -200,7 +223,7 @@ abstract class PlayerInjector {
|
|||||||
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||||
|
|
||||||
// Make sure we're listening
|
// Make sure we're listening
|
||||||
if (sendingFilters.contains(id)) {
|
if (id != null && sendingFilters.contains(id)) {
|
||||||
// A packet has been sent guys!
|
// A packet has been sent guys!
|
||||||
PacketContainer container = new PacketContainer(id, packet);
|
PacketContainer container = new PacketContainer(id, packet);
|
||||||
PacketEvent event = PacketEvent.fromServer(manager, container, player);
|
PacketEvent event = PacketEvent.fromServer(manager, container, player);
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.events.ListenerPriority;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a listener with a priority.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class PrioritizedListener<TListener> implements Comparable<PrioritizedListener<TListener>> {
|
||||||
|
|
||||||
|
private TListener listener;
|
||||||
|
private ListenerPriority priority;
|
||||||
|
|
||||||
|
public PrioritizedListener(TListener listener, ListenerPriority priority) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(PrioritizedListener<TListener> other) {
|
||||||
|
// This ensures that lower priority listeners are executed first
|
||||||
|
return Ints.compare(
|
||||||
|
this.getPriority().getSlot(),
|
||||||
|
other.getPriority().getSlot());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this equals() method is NOT consistent with compareTo().
|
||||||
|
// But, it's a private class so who cares.
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
// We only care about the listener - priority itself should not make a difference
|
||||||
|
if(obj instanceof PrioritizedListener){
|
||||||
|
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
|
||||||
|
return Objects.equal(listener, other.listener);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying listener.
|
||||||
|
* @return Underlying listener.
|
||||||
|
*/
|
||||||
|
public TListener getListener() {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the priority of this listener.
|
||||||
|
* @return Listener priority.
|
||||||
|
*/
|
||||||
|
public ListenerPriority getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,23 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
this.packetID = packetID;
|
this.packetID = packetID;
|
||||||
this.packetInjector = packetInjector;
|
this.packetInjector = packetInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove any packet overrides.
|
||||||
|
* @param packet - the packet to rever
|
||||||
|
*/
|
||||||
|
public void removeOverride(Packet packet) {
|
||||||
|
override.remove(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the packet that overrides the methods of the given packet.
|
||||||
|
* @param packet - the given packet.
|
||||||
|
* @return Overriden object.
|
||||||
|
*/
|
||||||
|
public Object getOverride(Packet packet) {
|
||||||
|
return override.get(packet);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
@ -60,9 +77,14 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
if (override.containsKey(thisObj)) {
|
if (override.containsKey(thisObj)) {
|
||||||
Object overridenObject = override.get(thisObj);
|
Object overridenObject = override.get(thisObj);
|
||||||
|
|
||||||
// Cancel EVERYTHING, including "processPacket"
|
// This packet has been cancelled
|
||||||
if (overridenObject == null)
|
if (overridenObject == null) {
|
||||||
return null;
|
// So, cancel all void methods
|
||||||
|
if (method.getReturnType().equals(Void.TYPE))
|
||||||
|
return null;
|
||||||
|
else // Revert to normal for everything else
|
||||||
|
overridenObject = thisObj;
|
||||||
|
}
|
||||||
|
|
||||||
returnValue = proxy.invokeSuper(overridenObject, args);
|
returnValue = proxy.invokeSuper(overridenObject, args);
|
||||||
} else {
|
} else {
|
||||||
@ -75,7 +97,7 @@ class ReadPacketModifier implements MethodInterceptor {
|
|||||||
|
|
||||||
// We need this in order to get the correct player
|
// We need this in order to get the correct player
|
||||||
DataInputStream input = (DataInputStream) args[0];
|
DataInputStream input = (DataInputStream) args[0];
|
||||||
|
|
||||||
// Let the people know
|
// Let the people know
|
||||||
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
|
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
|
||||||
PacketEvent event = packetInjector.packetRecieved(container, input);
|
PacketEvent event = packetInjector.packetRecieved(container, input);
|
||||||
|
136
ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java
Normale Datei
136
ProtocolLib/src/com/comphenix/protocol/injector/ReplacedArrayList.java
Normale Datei
@ -0,0 +1,136 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.collect.BiMap;
|
||||||
|
import com.google.common.collect.ForwardingList;
|
||||||
|
import com.google.common.collect.HashBiMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an array list that wraps another list, while automatically replacing one element with another.
|
||||||
|
* <p>
|
||||||
|
* The replaced elements can be recovered.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <TKey> - type of the elements we're replacing.
|
||||||
|
*/
|
||||||
|
class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
|
||||||
|
private BiMap<TKey, TKey> replaceMap = HashBiMap.create();
|
||||||
|
private List<TKey> underlyingList;
|
||||||
|
|
||||||
|
public ReplacedArrayList(List<TKey> underlyingList) {
|
||||||
|
this.underlyingList = underlyingList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(TKey element) {
|
||||||
|
if (replaceMap.containsKey(element)) {
|
||||||
|
return super.add(replaceMap.get(element));
|
||||||
|
} else {
|
||||||
|
return super.add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int index, TKey element) {
|
||||||
|
if (replaceMap.containsKey(element)) {
|
||||||
|
super.add(index, replaceMap.get(element));
|
||||||
|
} else {
|
||||||
|
super.add(index, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends TKey> collection) {
|
||||||
|
int oldSize = size();
|
||||||
|
|
||||||
|
for (TKey element : collection)
|
||||||
|
add(element);
|
||||||
|
return size() != oldSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(int index, Collection<? extends TKey> elements) {
|
||||||
|
int oldSize = size();
|
||||||
|
|
||||||
|
for (TKey element : elements)
|
||||||
|
add(index++, element);
|
||||||
|
return size() != oldSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<TKey> delegate() {
|
||||||
|
return underlyingList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a replace rule.
|
||||||
|
* <p>
|
||||||
|
* This automatically replaces every existing element.
|
||||||
|
* @param target - instance to find.
|
||||||
|
* @param replacement - instance to replace with.
|
||||||
|
*/
|
||||||
|
public synchronized void addMapping(TKey target, TKey replacement) {
|
||||||
|
replaceMap.put(target, replacement);
|
||||||
|
|
||||||
|
// Replace existing elements
|
||||||
|
replaceAll(target, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert the given mapping.
|
||||||
|
* @param target - the instance we replaced.
|
||||||
|
*/
|
||||||
|
public synchronized void removeMapping(TKey target) {
|
||||||
|
// Make sure the mapping exist
|
||||||
|
if (replaceMap.containsKey(target)) {
|
||||||
|
TKey replacement = replaceMap.get(target);
|
||||||
|
replaceMap.remove(target);
|
||||||
|
|
||||||
|
// Revert existing elements
|
||||||
|
replaceAll(replacement, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all instances of the given object.
|
||||||
|
* @param find - object to find.
|
||||||
|
* @param replace - object to replace it with.
|
||||||
|
*/
|
||||||
|
public synchronized void replaceAll(TKey find, TKey replace) {
|
||||||
|
for (int i = 0; i < underlyingList.size(); i++) {
|
||||||
|
if (Objects.equal(underlyingList.get(i), find))
|
||||||
|
underlyingList.set(i, replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undo all replacements.
|
||||||
|
*/
|
||||||
|
public synchronized void revertAll() {
|
||||||
|
|
||||||
|
// No need to do anything else
|
||||||
|
if (replaceMap.size() < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BiMap<TKey, TKey> inverse = replaceMap.inverse();
|
||||||
|
|
||||||
|
for (int i = 0; i < underlyingList.size(); i++) {
|
||||||
|
TKey replaced = underlyingList.get(i);
|
||||||
|
|
||||||
|
if (inverse.containsKey(replaced)) {
|
||||||
|
underlyingList.set(i, inverse.get(replaced));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
revertAll();
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||||
|
import com.comphenix.protocol.events.PacketAdapter;
|
||||||
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of synchronous packet listeners.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the given packet event for every registered listener.
|
||||||
|
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||||
|
* @param event - the packet event to invoke.
|
||||||
|
*/
|
||||||
|
public void invokePacketRecieving(Logger logger, PacketEvent event) {
|
||||||
|
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
|
||||||
|
|
||||||
|
if (list == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The returned list is thread-safe
|
||||||
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
|
try {
|
||||||
|
element.getListener().onPacketReceiving(event);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Minecraft doesn't want your Exception.
|
||||||
|
logger.log(Level.SEVERE,
|
||||||
|
"Exception occured in onPacketReceiving() for " +
|
||||||
|
PacketAdapter.getPluginName(element.getListener()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the given packet event for every registered listener.
|
||||||
|
* @param logger - the logger that will be used to inform about listener exceptions.
|
||||||
|
* @param event - the packet event to invoke.
|
||||||
|
*/
|
||||||
|
public void invokePacketSending(Logger logger, PacketEvent event) {
|
||||||
|
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketID());
|
||||||
|
|
||||||
|
if (list == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (PrioritizedListener<PacketListener> element : list) {
|
||||||
|
try {
|
||||||
|
element.getListener().onPacketSending(event);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Minecraft doesn't want your Exception.
|
||||||
|
logger.log(Level.SEVERE,
|
||||||
|
"Exception occured in onPacketSending() for " +
|
||||||
|
PacketAdapter.getPluginName(element.getListener()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,12 +17,17 @@
|
|||||||
|
|
||||||
package com.comphenix.protocol.injector;
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import net.minecraft.server.Packet;
|
import net.minecraft.server.Packet;
|
||||||
|
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||||
|
import com.comphenix.protocol.reflect.compiler.CompileListener;
|
||||||
|
import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caches structure modifiers.
|
* Caches structure modifiers.
|
||||||
@ -30,7 +35,10 @@ import com.comphenix.protocol.reflect.StructureModifier;
|
|||||||
*/
|
*/
|
||||||
public class StructureCache {
|
public class StructureCache {
|
||||||
// Structure modifiers
|
// Structure modifiers
|
||||||
private static Map<Integer, StructureModifier<Object>> structureModifiers = new HashMap<Integer, StructureModifier<Object>>();
|
private static ConcurrentMap<Integer, StructureModifier<Object>> structureModifiers =
|
||||||
|
new ConcurrentHashMap<Integer, StructureModifier<Object>>();
|
||||||
|
|
||||||
|
private static Set<Integer> compiling = new HashSet<Integer>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty Minecraft packet of the given ID.
|
* Creates an empty Minecraft packet of the given ID.
|
||||||
@ -53,15 +61,51 @@ public class StructureCache {
|
|||||||
* @return A structure modifier.
|
* @return A structure modifier.
|
||||||
*/
|
*/
|
||||||
public static StructureModifier<Object> getStructure(int id) {
|
public static StructureModifier<Object> getStructure(int id) {
|
||||||
|
// Compile structures by default
|
||||||
|
return getStructure(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a cached structure modifier for the given packet id.
|
||||||
|
* @param id - packet ID.
|
||||||
|
* @param compile - whether or not to asynchronously compile the structure modifier.
|
||||||
|
* @return A structure modifier.
|
||||||
|
*/
|
||||||
|
public static StructureModifier<Object> getStructure(int id, boolean compile) {
|
||||||
|
|
||||||
StructureModifier<Object> result = structureModifiers.get(id);
|
StructureModifier<Object> result = structureModifiers.get(id);
|
||||||
|
|
||||||
// Use the vanilla class definition
|
// We don't want to create this for every lookup
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = new StructureModifier<Object>(
|
// Use the vanilla class definition
|
||||||
|
final StructureModifier<Object> value = new StructureModifier<Object>(
|
||||||
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
|
MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true);
|
||||||
|
|
||||||
structureModifiers.put(id, result);
|
result = structureModifiers.putIfAbsent(id, value);
|
||||||
|
|
||||||
|
// We may end up creating multiple modifiers, but we'll agree on which to use
|
||||||
|
if (result == null) {
|
||||||
|
result = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically compile the structure modifier
|
||||||
|
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) {
|
||||||
|
compiler.scheduleCompilation(result, new CompileListener<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||||
|
structureModifiers.put(idCopy, compiledModifier);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compiling.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -19,8 +19,10 @@ package com.comphenix.protocol.reflect;
|
|||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -96,6 +98,7 @@ public class FuzzyReflection {
|
|||||||
* Retrieves a method by looking at its name.
|
* Retrieves a method by looking at its name.
|
||||||
* @param nameRegex - regular expression that will match method names.
|
* @param nameRegex - regular expression that will match method names.
|
||||||
* @return The first method that satisfies the regular expression.
|
* @return The first method that satisfies the regular expression.
|
||||||
|
* @throws RuntimeException If the method cannot be found.
|
||||||
*/
|
*/
|
||||||
public Method getMethodByName(String nameRegex) {
|
public Method getMethodByName(String nameRegex) {
|
||||||
|
|
||||||
@ -139,11 +142,38 @@ public class FuzzyReflection {
|
|||||||
* @return The first method that satisfies the parameter types.
|
* @return The first method that satisfies the parameter types.
|
||||||
*/
|
*/
|
||||||
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
|
public Method getMethodByParameters(String name, Class<?> returnType, Class<?>[] args) {
|
||||||
|
// Find the correct method to call
|
||||||
|
List<Method> methods = getMethodListByParameters(returnType, args);
|
||||||
|
|
||||||
|
if (methods.size() > 0) {
|
||||||
|
return methods.get(0);
|
||||||
|
} else {
|
||||||
|
// That sucks
|
||||||
|
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a method by looking at the parameter types and return type only.
|
||||||
|
* @param name - potential name of the method. Only used by the error mechanism.
|
||||||
|
* @param returnType - regular expression matching the return type of the method to find.
|
||||||
|
* @param args - regular expressions of the matching parameter types.
|
||||||
|
* @return The first method that satisfies the parameter types.
|
||||||
|
*/
|
||||||
|
public Method getMethodByParameters(String name, String returnTypeRegex, String[] argsRegex) {
|
||||||
|
|
||||||
|
Pattern match = Pattern.compile(returnTypeRegex);
|
||||||
|
Pattern[] argMatch = new Pattern[argsRegex.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < argsRegex.length; i++) {
|
||||||
|
argMatch[i] = Pattern.compile(argsRegex[i]);
|
||||||
|
}
|
||||||
|
|
||||||
// Find the correct method to call
|
// Find the correct method to call
|
||||||
for (Method method : getMethods()) {
|
for (Method method : getMethods()) {
|
||||||
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
if (match.matcher(method.getReturnType().getName()).matches()) {
|
||||||
return method;
|
if (matchParameters(argMatch, method.getParameterTypes()))
|
||||||
|
return method;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +181,39 @@ public class FuzzyReflection {
|
|||||||
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
throw new RuntimeException("Unable to find " + name + " in " + source.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean matchParameters(Pattern[] parameterMatchers, Class<?>[] argTypes) {
|
||||||
|
if (parameterMatchers.length != argTypes.length)
|
||||||
|
throw new IllegalArgumentException("Arrays must have the same cardinality.");
|
||||||
|
|
||||||
|
// Check types against the regular expressions
|
||||||
|
for (int i = 0; i < argTypes.length; i++) {
|
||||||
|
if (!parameterMatchers[i].matcher(argTypes[i].getName()).matches())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves every method that has the given parameter types and return type.
|
||||||
|
* @param returnType - return type of the method to find.
|
||||||
|
* @param args - parameter types of the method to find.
|
||||||
|
* @return Every method that satisfies the given constraints.
|
||||||
|
*/
|
||||||
|
public List<Method> getMethodListByParameters(Class<?> returnType, Class<?>[] args) {
|
||||||
|
|
||||||
|
List<Method> methods = new ArrayList<Method>();
|
||||||
|
|
||||||
|
// Find the correct method to call
|
||||||
|
for (Method method : getMethods()) {
|
||||||
|
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
|
||||||
|
methods.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a field by name.
|
* Retrieves a field by name.
|
||||||
* @param nameRegex - regular expression that will match a field name.
|
* @param nameRegex - regular expression that will match a field name.
|
||||||
@ -172,6 +235,46 @@ public class FuzzyReflection {
|
|||||||
nameRegex + " in " + source.getName());
|
nameRegex + " in " + source.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the first field with a type equal to or more specific to the given type.
|
||||||
|
* @param name - name the field probably is given. This will only be used in the error message.
|
||||||
|
* @param type - type of the field to find.
|
||||||
|
* @return The first field with a type that is an instance of the given type.
|
||||||
|
*/
|
||||||
|
public Field getFieldByType(String name, Class<?> type) {
|
||||||
|
|
||||||
|
List<Field> fields = getFieldListByType(type);
|
||||||
|
|
||||||
|
if (fields.size() > 0) {
|
||||||
|
return fields.get(0);
|
||||||
|
} else {
|
||||||
|
// Looks like we're outdated. Too bad.
|
||||||
|
throw new RuntimeException(String.format("Unable to find a field %s with the type %s in %s",
|
||||||
|
name, type.getName(), source.getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves every field with a type equal to or more specific to the given type.
|
||||||
|
* @param type - type of the fields to find.
|
||||||
|
* @return Every field with a type that is an instance of the given type.
|
||||||
|
*/
|
||||||
|
public List<Field> getFieldListByType(Class<?> type) {
|
||||||
|
|
||||||
|
List<Field> fields = new ArrayList<Field>();
|
||||||
|
|
||||||
|
// Field with a compatible type
|
||||||
|
for (Field field : getFields()) {
|
||||||
|
// A assignable from B -> B instanceOf A
|
||||||
|
if (type.isAssignableFrom(field.getType())) {
|
||||||
|
fields.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a field by type.
|
* Retrieves a field by type.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -21,34 +21,45 @@ import java.lang.reflect.Field;
|
|||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides list-oriented access to the fields of a Minecraft packet.
|
||||||
|
* <p>
|
||||||
|
* Implemented by using reflection. Use a CompiledStructureModifier, if speed is essential.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <TField> Type of the fields to retrieve.
|
||||||
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class StructureModifier<TField> {
|
public class StructureModifier<TField> {
|
||||||
|
|
||||||
// Object and its type
|
// Object and its type
|
||||||
private Class targetType;
|
protected Class targetType;
|
||||||
private Object target;
|
protected Object target;
|
||||||
|
|
||||||
// Converter. May be NULL.
|
// Converter. May be NULL.
|
||||||
private EquivalentConverter<TField> converter;
|
protected EquivalentConverter<TField> converter;
|
||||||
|
|
||||||
// The fields to read in order
|
// The fields to read in order
|
||||||
private Class fieldType;
|
protected Class fieldType;
|
||||||
private List<Field> data = new ArrayList<Field>();
|
protected List<Field> data = new ArrayList<Field>();
|
||||||
|
|
||||||
// Improved default values
|
// Improved default values
|
||||||
private Set<Field> defaultFields;
|
protected Map<Field, Integer> defaultFields;
|
||||||
|
|
||||||
// Cache of previous types
|
// Cache of previous types
|
||||||
private Map<Class, StructureModifier> subtypeCache;
|
protected Map<Class, StructureModifier> subtypeCache;
|
||||||
|
|
||||||
|
// Whether or subclasses should handle conversion
|
||||||
|
protected boolean customConvertHandling;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a structure modifier.
|
* Creates a structure modifier.
|
||||||
@ -58,22 +69,38 @@ public class StructureModifier<TField> {
|
|||||||
*/
|
*/
|
||||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
||||||
List<Field> fields = getFields(targetType, superclassExclude);
|
List<Field> fields = getFields(targetType, superclassExclude);
|
||||||
Set<Field> defaults = requireDefault ? generateDefaultFields(fields) : new HashSet<Field>();
|
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
||||||
|
|
||||||
initialize(targetType, Object.class, fields, defaults, null, new HashMap<Class, StructureModifier>());
|
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private StructureModifier(StructureModifier<TField> other, Object target) {
|
/**
|
||||||
initialize(other.targetType, other.fieldType, other.data, other.defaultFields, other.converter, other.subtypeCache);
|
* Consumers of this method should call "initialize".
|
||||||
this.target = target;
|
*/
|
||||||
|
protected StructureModifier() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StructureModifier() {
|
/**
|
||||||
// Consumers of this method should call "initialize"
|
* Initialize using the same field types.
|
||||||
|
* @param other - information to set.
|
||||||
|
*/
|
||||||
|
protected void initialize(StructureModifier<TField> other) {
|
||||||
|
initialize(other.targetType, other.fieldType, other.data,
|
||||||
|
other.defaultFields, other.converter, other.subtypeCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize(Class targetType, Class fieldType,
|
/**
|
||||||
List<Field> data, Set<Field> defaultFields,
|
* Initialize every field of this class.
|
||||||
|
* @param targetType - type of the object we're reading and writing from.
|
||||||
|
* @param fieldType - the common type of the fields we're modifying.
|
||||||
|
* @param data - list of fields to modify.
|
||||||
|
* @param defaultFields - list of fields that will be automatically initialized.
|
||||||
|
* @param converter - converts between the common field type and the actual type the consumer expects.
|
||||||
|
* @param subTypeCache - a structure modifier cache.
|
||||||
|
*/
|
||||||
|
protected void initialize(Class targetType, Class fieldType,
|
||||||
|
List<Field> data, Map<Field, Integer> defaultFields,
|
||||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||||
this.targetType = targetType;
|
this.targetType = targetType;
|
||||||
this.fieldType = fieldType;
|
this.fieldType = fieldType;
|
||||||
@ -101,7 +128,7 @@ public class StructureModifier<TField> {
|
|||||||
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
|
||||||
|
|
||||||
// Use the converter, if we have it
|
// Use the converter, if we have it
|
||||||
if (converter != null)
|
if (needConversion())
|
||||||
return converter.getSpecific(result);
|
return converter.getSpecific(result);
|
||||||
else
|
else
|
||||||
return (TField) result;
|
return (TField) result;
|
||||||
@ -140,7 +167,7 @@ public class StructureModifier<TField> {
|
|||||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||||
|
|
||||||
// Use the converter, if it exists
|
// Use the converter, if it exists
|
||||||
Object obj = converter != null ? converter.getGeneric(value) : value;
|
Object obj = needConversion() ? converter.getGeneric(value) : value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||||
@ -152,6 +179,14 @@ public class StructureModifier<TField> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not we should use the converter instance.
|
||||||
|
* @return TRUE if we should, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private final boolean needConversion() {
|
||||||
|
return converter != null && !customConvertHandling;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the value of a given field IF and ONLY if it exists.
|
* Writes the value of a given field IF and ONLY if it exists.
|
||||||
* @param fieldIndex - index of the potential field.
|
* @param fieldIndex - index of the potential field.
|
||||||
@ -197,7 +232,7 @@ public class StructureModifier<TField> {
|
|||||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
|
||||||
// Write a default instance to every field
|
// Write a default instance to every field
|
||||||
for (Field field : defaultFields) {
|
for (Field field : defaultFields.keySet()) {
|
||||||
try {
|
try {
|
||||||
FieldUtils.writeField(field, target,
|
FieldUtils.writeField(field, target,
|
||||||
generator.getDefault(field.getType()), true);
|
generator.getDefault(field.getType()), true);
|
||||||
@ -223,24 +258,32 @@ public class StructureModifier<TField> {
|
|||||||
// Do we need to update the cache?
|
// Do we need to update the cache?
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
List<Field> filtered = new ArrayList<Field>();
|
List<Field> filtered = new ArrayList<Field>();
|
||||||
Set<Field> defaults = new HashSet<Field>();
|
Map<Field, Integer> defaults = new HashMap<Field, Integer>();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
for (Field field : data) {
|
for (Field field : data) {
|
||||||
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
|
||||||
filtered.add(field);
|
filtered.add(field);
|
||||||
|
|
||||||
if (defaultFields.contains(field))
|
// Don't use the original index
|
||||||
defaults.add(field);
|
if (defaultFields.containsKey(field))
|
||||||
|
defaults.put(field, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep track of the field index
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache structure modifiers
|
// Cache structure modifiers
|
||||||
result = new StructureModifier<T>();
|
result = withFieldType(fieldType, filtered, defaults, converter);
|
||||||
result.initialize(targetType, fieldType, filtered, defaults,
|
|
||||||
converter, new HashMap<Class, StructureModifier>());
|
|
||||||
|
|
||||||
if (fieldType != null)
|
if (fieldType != null) {
|
||||||
subtypeCache.put(fieldType, result);
|
subtypeCache.put(fieldType, result);
|
||||||
|
|
||||||
|
// Automatically compile the structure modifier
|
||||||
|
if (BackgroundCompiler.getInstance() != null)
|
||||||
|
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the target too
|
// Add the target too
|
||||||
@ -296,28 +339,59 @@ public class StructureModifier<TField> {
|
|||||||
return data.size();
|
return data.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new structure modifier for the new field type.
|
||||||
|
* @param fieldType - common type of each field.
|
||||||
|
* @param filtered - list of fields after filtering the original modifier.
|
||||||
|
* @param defaults - list of default values after filtering the original.
|
||||||
|
* @param converter - the new converter.
|
||||||
|
* @return A new structure modifier.
|
||||||
|
*/
|
||||||
|
protected <T> StructureModifier<T> withFieldType(
|
||||||
|
Class fieldType, List<Field> filtered,
|
||||||
|
Map<Field, Integer> defaults, EquivalentConverter<T> converter) {
|
||||||
|
|
||||||
|
StructureModifier<T> result = new StructureModifier<T>();
|
||||||
|
result.initialize(targetType, fieldType, filtered, defaults,
|
||||||
|
converter, new ConcurrentHashMap<Class, StructureModifier>());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a structure modifier of the same type for a different object target.
|
* Retrieves a structure modifier of the same type for a different object target.
|
||||||
* @param target - different target of the same type.
|
* @param target - different target of the same type.
|
||||||
* @return Structure modifier with the new target.
|
* @return Structure modifier with the new target.
|
||||||
*/
|
*/
|
||||||
public StructureModifier<TField> withTarget(Object target) {
|
public StructureModifier<TField> withTarget(Object target) {
|
||||||
return new StructureModifier<TField>(this, target);
|
StructureModifier<TField> copy = new StructureModifier<TField>();
|
||||||
|
|
||||||
|
// Create a new instance
|
||||||
|
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
|
||||||
|
copy.target = target;
|
||||||
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a structure modifier with the same type and target, but using a new object converter.
|
* Retrieves a structure modifier with the same type and target, but using a new object converter.
|
||||||
* @param converter- the object converter to use.
|
* @param converter - the object converter to use.
|
||||||
* @return Structure modifier with the new converter.
|
* @return Structure modifier with the new converter.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
|
private <T> StructureModifier<T> withConverter(EquivalentConverter<T> converter) {
|
||||||
StructureModifier copy = new StructureModifier(this, target);
|
StructureModifier copy = withTarget(target);
|
||||||
|
|
||||||
copy.converter = converter;
|
copy.setConverter(converter);
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current object converter. Should only be called during construction.
|
||||||
|
* @param converter - current object converter.
|
||||||
|
*/
|
||||||
|
protected void setConverter(EquivalentConverter<TField> converter) {
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
* Retrieves a list of the fields matching the constraints of this structure modifier.
|
||||||
* @return List of fields.
|
* @return List of fields.
|
||||||
@ -325,7 +399,7 @@ public class StructureModifier<TField> {
|
|||||||
public List<Field> getFields() {
|
public List<Field> getFields() {
|
||||||
return ImmutableList.copyOf(data);
|
return ImmutableList.copyOf(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve every value stored in the fields of the current type.
|
* Retrieve every value stored in the fields of the current type.
|
||||||
* @return Every field value.
|
* @return Every field value.
|
||||||
@ -334,17 +408,19 @@ public class StructureModifier<TField> {
|
|||||||
public List<TField> getValues() throws FieldAccessException {
|
public List<TField> getValues() throws FieldAccessException {
|
||||||
List<TField> values = new ArrayList<TField>();
|
List<TField> values = new ArrayList<TField>();
|
||||||
|
|
||||||
for (int i = 0; i < size(); i++)
|
for (int i = 0; i < size(); i++) {
|
||||||
values.add(read(i));
|
values.add(read(i));
|
||||||
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to generate plausible default values
|
// Used to generate plausible default values
|
||||||
private static Set<Field> generateDefaultFields(List<Field> fields) {
|
private static Map<Field, Integer> generateDefaultFields(List<Field> fields) {
|
||||||
|
|
||||||
Set<Field> requireDefaults = new HashSet<Field>();
|
Map<Field, Integer> requireDefaults = new HashMap<Field, Integer>();
|
||||||
DefaultInstances generator = DefaultInstances.DEFAULT;
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
Class<?> type = field.getType();
|
Class<?> type = field.getType();
|
||||||
@ -354,9 +430,12 @@ public class StructureModifier<TField> {
|
|||||||
// Next, see if we actually can generate a default value
|
// Next, see if we actually can generate a default value
|
||||||
if (generator.getDefault(type) != null) {
|
if (generator.getDefault(type) != null) {
|
||||||
// If so, require it
|
// If so, require it
|
||||||
requireDefaults.add(field);
|
requireDefaults.put(field, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increment field index
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return requireDefaults;
|
return requireDefaults;
|
||||||
|
@ -155,6 +155,13 @@ public class VolatileField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether or not we'll need to revert the value.
|
||||||
|
*/
|
||||||
|
public boolean isCurrentSet() {
|
||||||
|
return currentSet;
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureLoaded() {
|
private void ensureLoaded() {
|
||||||
// Load the value if we haven't already
|
// Load the value if we haven't already
|
||||||
if (!previousLoaded) {
|
if (!previousLoaded) {
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles structure modifiers on a background thread.
|
||||||
|
* <p>
|
||||||
|
* This is necessary as we cannot block the main thread.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class BackgroundCompiler {
|
||||||
|
|
||||||
|
// How long to wait for a shutdown
|
||||||
|
public static final int SHUTDOWN_DELAY_MS = 2000;
|
||||||
|
|
||||||
|
// The single background compiler we're using
|
||||||
|
private static BackgroundCompiler backgroundCompiler;
|
||||||
|
|
||||||
|
private StructureCompiler compiler;
|
||||||
|
private boolean enabled;
|
||||||
|
private boolean shuttingDown;
|
||||||
|
|
||||||
|
private ExecutorService executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current background compiler.
|
||||||
|
* @return Current background compiler.
|
||||||
|
*/
|
||||||
|
public static BackgroundCompiler getInstance() {
|
||||||
|
return backgroundCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the single background compiler we're using.
|
||||||
|
* @param backgroundCompiler - current background compiler, or NULL if the library is not loaded.
|
||||||
|
*/
|
||||||
|
public static void setInstance(BackgroundCompiler backgroundCompiler) {
|
||||||
|
BackgroundCompiler.backgroundCompiler = backgroundCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a background compiler.
|
||||||
|
* @param loader - class loader from Bukkit.
|
||||||
|
*/
|
||||||
|
public BackgroundCompiler(ClassLoader loader) {
|
||||||
|
this(loader, Executors.newSingleThreadExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a background compiler utilizing the given thread pool.
|
||||||
|
* @param loader - class loader from Bukkit.
|
||||||
|
* @param executor - thread pool we'll use.
|
||||||
|
*/
|
||||||
|
public BackgroundCompiler(ClassLoader loader, ExecutorService executor) {
|
||||||
|
if (loader == null)
|
||||||
|
throw new IllegalArgumentException("loader cannot be NULL");
|
||||||
|
if (executor == null)
|
||||||
|
throw new IllegalArgumentException("executor cannot be NULL");
|
||||||
|
|
||||||
|
this.compiler = new StructureCompiler(loader);
|
||||||
|
this.executor = executor;
|
||||||
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the indirectly given structure modifier is eventually compiled.
|
||||||
|
* @param cache - store of structure modifiers.
|
||||||
|
* @param key - key of the structure modifier to compile.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public void scheduleCompilation(final Map<Class, StructureModifier> cache, final Class key) {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final StructureModifier<Object> uncompiled = cache.get(key);
|
||||||
|
|
||||||
|
if (uncompiled != null) {
|
||||||
|
scheduleCompilation(uncompiled, new CompileListener<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onCompiled(StructureModifier<Object> compiledModifier) {
|
||||||
|
// Update cache
|
||||||
|
cache.put(key, compiledModifier);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the given structure modifier is eventually compiled.
|
||||||
|
* @param uncompiled - structure modifier to compile.
|
||||||
|
* @param listener - listener responsible for responding to the compilation.
|
||||||
|
*/
|
||||||
|
public <TKey> void scheduleCompilation(final StructureModifier<TKey> uncompiled, final CompileListener<TKey> listener) {
|
||||||
|
|
||||||
|
// Only schedule if we're enabled
|
||||||
|
if (enabled && !shuttingDown) {
|
||||||
|
|
||||||
|
// Don't try to schedule anything
|
||||||
|
if (executor == null || executor.isShutdown())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
executor.submit(new Callable<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object call() throws Exception {
|
||||||
|
|
||||||
|
StructureModifier<TKey> modifier = uncompiled;
|
||||||
|
|
||||||
|
// Do our compilation
|
||||||
|
modifier = compiler.compile(modifier);
|
||||||
|
listener.onCompiled(modifier);
|
||||||
|
|
||||||
|
// We'll also return the new structure modifier
|
||||||
|
return modifier;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
// Occures when the underlying queue is overflowing. Since the compilation
|
||||||
|
// is only an optmization and not really essential we'll just log this failure
|
||||||
|
// and move on.
|
||||||
|
Logger.getLogger("Minecraft").log(Level.WARNING, "Unable to schedule compilation task.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up after ourselves using the default timeout.
|
||||||
|
*/
|
||||||
|
public void shutdownAll() {
|
||||||
|
shutdownAll(SHUTDOWN_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up after ourselves.
|
||||||
|
* @param timeout - the maximum time to wait.
|
||||||
|
* @param unit - the time unit of the timeout argument.
|
||||||
|
*/
|
||||||
|
public void shutdownAll(long timeout, TimeUnit unit) {
|
||||||
|
setEnabled(false);
|
||||||
|
shuttingDown = true;
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
executor.awaitTermination(timeout, unit);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Unlikely to ever occur.
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether or not the background compiler is enabled.
|
||||||
|
* @return TRUE if it is enabled, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether or not the background compiler is enabled.
|
||||||
|
* @param enabled - TRUE to enable it, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current structure compiler.
|
||||||
|
* @return Current structure compiler.
|
||||||
|
*/
|
||||||
|
public StructureCompiler getCompiler() {
|
||||||
|
return compiler;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,275 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the compiler to automatically box and unbox values.
|
||||||
|
*/
|
||||||
|
class BoxingHelper {
|
||||||
|
|
||||||
|
private final static Type BYTE_TYPE = Type.getObjectType("java/lang/Byte");
|
||||||
|
private final static Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean");
|
||||||
|
private final static Type SHORT_TYPE = Type.getObjectType("java/lang/Short");
|
||||||
|
private final static Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character");
|
||||||
|
private final static Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer");
|
||||||
|
private final static Type FLOAT_TYPE = Type.getObjectType("java/lang/Float");
|
||||||
|
private final static Type LONG_TYPE = Type.getObjectType("java/lang/Long");
|
||||||
|
private final static Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double");
|
||||||
|
private final static Type NUMBER_TYPE = Type.getObjectType("java/lang/Number");
|
||||||
|
private final static Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
|
||||||
|
|
||||||
|
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
|
||||||
|
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
|
||||||
|
private final static MethodDescriptor INT_VALUE = MethodDescriptor.getMethod("int intValue()");
|
||||||
|
private final static MethodDescriptor FLOAT_VALUE = MethodDescriptor.getMethod("float floatValue()");
|
||||||
|
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
|
||||||
|
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
|
||||||
|
|
||||||
|
private MethodVisitor mv;
|
||||||
|
|
||||||
|
public BoxingHelper(MethodVisitor mv) {
|
||||||
|
this.mv = mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instructions to box the top stack value. This value is
|
||||||
|
* replaced by its boxed equivalent on top of the stack.
|
||||||
|
*
|
||||||
|
* @param type the type of the top stack value.
|
||||||
|
*/
|
||||||
|
public void box(final Type type){
|
||||||
|
if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == Type.VOID_TYPE) {
|
||||||
|
push((String) null);
|
||||||
|
} else {
|
||||||
|
Type boxed = type;
|
||||||
|
|
||||||
|
switch(type.getSort()) {
|
||||||
|
case Type.BYTE:
|
||||||
|
boxed = BYTE_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.BOOLEAN:
|
||||||
|
boxed = BOOLEAN_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.SHORT:
|
||||||
|
boxed = SHORT_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.CHAR:
|
||||||
|
boxed = CHARACTER_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.INT:
|
||||||
|
boxed = INTEGER_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.FLOAT:
|
||||||
|
boxed = FLOAT_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.LONG:
|
||||||
|
boxed = LONG_TYPE;
|
||||||
|
break;
|
||||||
|
case Type.DOUBLE:
|
||||||
|
boxed = DOUBLE_TYPE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
newInstance(boxed);
|
||||||
|
if(type.getSize() == 2) {
|
||||||
|
// Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o
|
||||||
|
dupX2();
|
||||||
|
dupX2();
|
||||||
|
pop();
|
||||||
|
} else {
|
||||||
|
// p -> po -> opo -> oop -> o
|
||||||
|
dupX1();
|
||||||
|
swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to invoke a constructor.
|
||||||
|
*
|
||||||
|
* @param type the class in which the constructor is defined.
|
||||||
|
* @param method the constructor to be invoked.
|
||||||
|
*/
|
||||||
|
public void invokeConstructor(final Type type, final MethodDescriptor method){
|
||||||
|
invokeInsn(Opcodes.INVOKESPECIAL, type, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a DUP_X1 instruction.
|
||||||
|
*/
|
||||||
|
public void dupX1(){
|
||||||
|
mv.visitInsn(Opcodes.DUP_X1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a DUP_X2 instruction.
|
||||||
|
*/
|
||||||
|
public void dupX2(){
|
||||||
|
mv.visitInsn(Opcodes.DUP_X2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a POP instruction.
|
||||||
|
*/
|
||||||
|
public void pop(){
|
||||||
|
mv.visitInsn(Opcodes.POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a SWAP instruction.
|
||||||
|
*/
|
||||||
|
public void swap(){
|
||||||
|
mv.visitInsn(Opcodes.SWAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to push the given value on the stack.
|
||||||
|
*
|
||||||
|
* @param value the value to be pushed on the stack.
|
||||||
|
*/
|
||||||
|
public void push(final boolean value){
|
||||||
|
push(value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to push the given value on the stack.
|
||||||
|
*
|
||||||
|
* @param value the value to be pushed on the stack.
|
||||||
|
*/
|
||||||
|
public void push(final int value) {
|
||||||
|
if (value >= -1 && value <= 5) {
|
||||||
|
mv.visitInsn(Opcodes.ICONST_0 + value);
|
||||||
|
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
|
||||||
|
mv.visitIntInsn(Opcodes.BIPUSH, value);
|
||||||
|
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
|
||||||
|
mv.visitIntInsn(Opcodes.SIPUSH, value);
|
||||||
|
} else {
|
||||||
|
mv.visitLdcInsn(new Integer(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to create a new object.
|
||||||
|
*
|
||||||
|
* @param type the class of the object to be created.
|
||||||
|
*/
|
||||||
|
public void newInstance(final Type type){
|
||||||
|
typeInsn(Opcodes.NEW, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to push the given value on the stack.
|
||||||
|
*
|
||||||
|
* @param value the value to be pushed on the stack. May be <tt>null</tt>.
|
||||||
|
*/
|
||||||
|
public void push(final String value) {
|
||||||
|
if (value == null) {
|
||||||
|
mv.visitInsn(Opcodes.ACONST_NULL);
|
||||||
|
} else {
|
||||||
|
mv.visitLdcInsn(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instructions to unbox the top stack value. This value is
|
||||||
|
* replaced by its unboxed equivalent on top of the stack.
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* the type of the top stack value.
|
||||||
|
*/
|
||||||
|
public void unbox(final Type type){
|
||||||
|
Type t = NUMBER_TYPE;
|
||||||
|
MethodDescriptor sig = null;
|
||||||
|
|
||||||
|
switch(type.getSort()) {
|
||||||
|
case Type.VOID:
|
||||||
|
return;
|
||||||
|
case Type.CHAR:
|
||||||
|
t = CHARACTER_TYPE;
|
||||||
|
sig = CHAR_VALUE;
|
||||||
|
break;
|
||||||
|
case Type.BOOLEAN:
|
||||||
|
t = BOOLEAN_TYPE;
|
||||||
|
sig = BOOLEAN_VALUE;
|
||||||
|
break;
|
||||||
|
case Type.DOUBLE:
|
||||||
|
sig = DOUBLE_VALUE;
|
||||||
|
break;
|
||||||
|
case Type.FLOAT:
|
||||||
|
sig = FLOAT_VALUE;
|
||||||
|
break;
|
||||||
|
case Type.LONG:
|
||||||
|
sig = LONG_VALUE;
|
||||||
|
break;
|
||||||
|
case Type.INT:
|
||||||
|
case Type.SHORT:
|
||||||
|
case Type.BYTE:
|
||||||
|
sig = INT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sig == null) {
|
||||||
|
checkCast(type);
|
||||||
|
} else {
|
||||||
|
checkCast(t);
|
||||||
|
invokeVirtual(t, sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to check that the top stack value is of the
|
||||||
|
* given type.
|
||||||
|
*
|
||||||
|
* @param type a class or interface type.
|
||||||
|
*/
|
||||||
|
public void checkCast(final Type type){
|
||||||
|
if(!type.equals(OBJECT_TYPE)) {
|
||||||
|
typeInsn(Opcodes.CHECKCAST, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the instruction to invoke a normal method.
|
||||||
|
*
|
||||||
|
* @param owner the class in which the method is defined.
|
||||||
|
* @param method the method to be invoked.
|
||||||
|
*/
|
||||||
|
public void invokeVirtual(final Type owner, final MethodDescriptor method){
|
||||||
|
invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an invoke method instruction.
|
||||||
|
*
|
||||||
|
* @param opcode the instruction's opcode.
|
||||||
|
* @param type the class in which the method is defined.
|
||||||
|
* @param method the method to be invoked.
|
||||||
|
*/
|
||||||
|
private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
|
||||||
|
String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
|
||||||
|
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a type dependent instruction.
|
||||||
|
*
|
||||||
|
* @param opcode the instruction's opcode.
|
||||||
|
* @param type the instruction's operand.
|
||||||
|
*/
|
||||||
|
private void typeInsn(final int opcode, final Type type){
|
||||||
|
String desc;
|
||||||
|
|
||||||
|
if(type.getSort() == Type.ARRAY) {
|
||||||
|
desc = type.getDescriptor();
|
||||||
|
} else {
|
||||||
|
desc = type.getInternalName();
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitTypeInsn(opcode, desc);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to save the result of an compilation.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <TKey> - type of the structure modifier field.
|
||||||
|
*/
|
||||||
|
public interface CompileListener<TKey> {
|
||||||
|
/**
|
||||||
|
* Invoked when a structure modifier has been successfully compiled.
|
||||||
|
* @param compiledModifier - the compiled structure modifier.
|
||||||
|
*/
|
||||||
|
public void onCompiled(StructureModifier<TKey> compiledModifier);
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a compiled structure modifier.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <TField> Field type.
|
||||||
|
*/
|
||||||
|
public abstract class CompiledStructureModifier<TField> extends StructureModifier<TField> {
|
||||||
|
// Used to compile instances of structure modifiers
|
||||||
|
protected StructureCompiler compiler;
|
||||||
|
|
||||||
|
public CompiledStructureModifier() {
|
||||||
|
super();
|
||||||
|
customConvertHandling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed up the default writer
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public StructureModifier<TField> writeDefaults() throws FieldAccessException {
|
||||||
|
|
||||||
|
DefaultInstances generator = DefaultInstances.DEFAULT;
|
||||||
|
|
||||||
|
// Write a default instance to every field
|
||||||
|
for (Map.Entry<Field, Integer> entry : defaultFields.entrySet()) {
|
||||||
|
Integer index = entry.getValue();
|
||||||
|
Field field = entry.getKey();
|
||||||
|
|
||||||
|
write(index, (TField) generator.getDefault(field.getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public final TField read(int fieldIndex) throws FieldAccessException {
|
||||||
|
Object result = readGenerated(fieldIndex);
|
||||||
|
|
||||||
|
if (converter != null)
|
||||||
|
return converter.getSpecific(result);
|
||||||
|
else
|
||||||
|
return (TField) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
|
||||||
|
if (converter != null)
|
||||||
|
value = converter.getGeneric((TField) value);
|
||||||
|
return writeGenerated(index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StructureModifier<TField> withTarget(Object target) {
|
||||||
|
if (compiler != null)
|
||||||
|
return compiler.compile(super.withTarget(target));
|
||||||
|
else
|
||||||
|
return super.withTarget(target);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a method.
|
||||||
|
*/
|
||||||
|
class MethodDescriptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method name.
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method descriptor.
|
||||||
|
*/
|
||||||
|
private final String desc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps primitive Java type names to their descriptors.
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> DESCRIPTORS;
|
||||||
|
|
||||||
|
static {
|
||||||
|
DESCRIPTORS = new HashMap<String, String>();
|
||||||
|
DESCRIPTORS.put("void", "V");
|
||||||
|
DESCRIPTORS.put("byte", "B");
|
||||||
|
DESCRIPTORS.put("char", "C");
|
||||||
|
DESCRIPTORS.put("double", "D");
|
||||||
|
DESCRIPTORS.put("float", "F");
|
||||||
|
DESCRIPTORS.put("int", "I");
|
||||||
|
DESCRIPTORS.put("long", "J");
|
||||||
|
DESCRIPTORS.put("short", "S");
|
||||||
|
DESCRIPTORS.put("boolean", "Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Method}.
|
||||||
|
*
|
||||||
|
* @param name the method's name.
|
||||||
|
* @param desc the method's descriptor.
|
||||||
|
*/
|
||||||
|
public MethodDescriptor(final String name, final String desc) {
|
||||||
|
this.name = name;
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Method}.
|
||||||
|
*
|
||||||
|
* @param name the method's name.
|
||||||
|
* @param returnType the method's return type.
|
||||||
|
* @param argumentTypes the method's argument types.
|
||||||
|
*/
|
||||||
|
public MethodDescriptor(
|
||||||
|
final String name,
|
||||||
|
final Type returnType,
|
||||||
|
final Type[] argumentTypes)
|
||||||
|
{
|
||||||
|
this(name, Type.getMethodDescriptor(returnType, argumentTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Method} corresponding to the given Java method
|
||||||
|
* declaration.
|
||||||
|
*
|
||||||
|
* @param method a Java method declaration, without argument names, of the
|
||||||
|
* form "returnType name (argumentType1, ... argumentTypeN)", where
|
||||||
|
* the types are in plain Java (e.g. "int", "float",
|
||||||
|
* "java.util.List", ...). Classes of the java.lang package can be
|
||||||
|
* specified by their unqualified name; all other classes names must
|
||||||
|
* be fully qualified.
|
||||||
|
* @return a {@link Method} corresponding to the given Java method
|
||||||
|
* declaration.
|
||||||
|
* @throws IllegalArgumentException if <code>method</code> could not get
|
||||||
|
* parsed.
|
||||||
|
*/
|
||||||
|
public static MethodDescriptor getMethod(final String method)
|
||||||
|
throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
return getMethod(method, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Method} corresponding to the given Java method
|
||||||
|
* declaration.
|
||||||
|
*
|
||||||
|
* @param method a Java method declaration, without argument names, of the
|
||||||
|
* form "returnType name (argumentType1, ... argumentTypeN)", where
|
||||||
|
* the types are in plain Java (e.g. "int", "float",
|
||||||
|
* "java.util.List", ...). Classes of the java.lang package may be
|
||||||
|
* specified by their unqualified name, depending on the
|
||||||
|
* defaultPackage argument; all other classes names must be fully
|
||||||
|
* qualified.
|
||||||
|
* @param defaultPackage true if unqualified class names belong to the
|
||||||
|
* default package, or false if they correspond to java.lang classes.
|
||||||
|
* For instance "Object" means "Object" if this option is true, or
|
||||||
|
* "java.lang.Object" otherwise.
|
||||||
|
* @return a {@link Method} corresponding to the given Java method
|
||||||
|
* declaration.
|
||||||
|
* @throws IllegalArgumentException if <code>method</code> could not get
|
||||||
|
* parsed.
|
||||||
|
*/
|
||||||
|
public static MethodDescriptor getMethod(
|
||||||
|
final String method,
|
||||||
|
final boolean defaultPackage) throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
int space = method.indexOf(' ');
|
||||||
|
int start = method.indexOf('(', space) + 1;
|
||||||
|
int end = method.indexOf(')', start);
|
||||||
|
if (space == -1 || start == -1 || end == -1) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
String returnType = method.substring(0, space);
|
||||||
|
String methodName = method.substring(space + 1, start - 1).trim();
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append('(');
|
||||||
|
int p;
|
||||||
|
do {
|
||||||
|
String s;
|
||||||
|
p = method.indexOf(',', start);
|
||||||
|
if (p == -1) {
|
||||||
|
s = map(method.substring(start, end).trim(), defaultPackage);
|
||||||
|
} else {
|
||||||
|
s = map(method.substring(start, p).trim(), defaultPackage);
|
||||||
|
start = p + 1;
|
||||||
|
}
|
||||||
|
sb.append(s);
|
||||||
|
} while (p != -1);
|
||||||
|
sb.append(')');
|
||||||
|
sb.append(map(returnType, defaultPackage));
|
||||||
|
return new MethodDescriptor(methodName, sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String map(final String type, final boolean defaultPackage) {
|
||||||
|
if ("".equals(type)) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
int index = 0;
|
||||||
|
while ((index = type.indexOf("[]", index) + 1) > 0) {
|
||||||
|
sb.append('[');
|
||||||
|
}
|
||||||
|
|
||||||
|
String t = type.substring(0, type.length() - sb.length() * 2);
|
||||||
|
String desc = (String) DESCRIPTORS.get(t);
|
||||||
|
if (desc != null) {
|
||||||
|
sb.append(desc);
|
||||||
|
} else {
|
||||||
|
sb.append('L');
|
||||||
|
if (t.indexOf('.') < 0) {
|
||||||
|
if (!defaultPackage) {
|
||||||
|
sb.append("java/lang/");
|
||||||
|
}
|
||||||
|
sb.append(t);
|
||||||
|
} else {
|
||||||
|
sb.append(t.replace('.', '/'));
|
||||||
|
}
|
||||||
|
sb.append(';');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the method described by this object.
|
||||||
|
*
|
||||||
|
* @return the name of the method described by this object.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the descriptor of the method described by this object.
|
||||||
|
*
|
||||||
|
* @return the descriptor of the method described by this object.
|
||||||
|
*/
|
||||||
|
public String getDescriptor() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the return type of the method described by this object.
|
||||||
|
*
|
||||||
|
* @return the return type of the method described by this object.
|
||||||
|
*/
|
||||||
|
public Type getReturnType() {
|
||||||
|
return Type.getReturnType(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the argument types of the method described by this object.
|
||||||
|
*
|
||||||
|
* @return the argument types of the method described by this object.
|
||||||
|
*/
|
||||||
|
public Type[] getArgumentTypes() {
|
||||||
|
return Type.getArgumentTypes(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return name + desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (!(o instanceof MethodDescriptor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MethodDescriptor other = (MethodDescriptor) o;
|
||||||
|
return name.equals(other.name) && desc.equals(other.desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return name.hashCode() ^ desc.hashCode();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,443 @@
|
|||||||
|
package com.comphenix.protocol.reflect.compiler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.PrimitiveUtils;
|
||||||
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
|
import net.sf.cglib.asm.*;
|
||||||
|
|
||||||
|
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
|
||||||
|
//
|
||||||
|
// private Packet20NamedEntitySpawn typedTarget;
|
||||||
|
//
|
||||||
|
// public CompiledStructureModifierPacket20(StructureModifier<TField> other, StructureCompiler compiler) {
|
||||||
|
// super();
|
||||||
|
// initialize(other);
|
||||||
|
// this.target = other.getTarget();
|
||||||
|
// this.typedTarget = (Packet20NamedEntitySpawn) target;
|
||||||
|
// this.compiler = compiler;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// protected Object readGenerated(int fieldIndex) throws FieldAccessException {
|
||||||
|
//
|
||||||
|
// Packet20NamedEntitySpawn target = typedTarget;
|
||||||
|
//
|
||||||
|
// switch (fieldIndex) {
|
||||||
|
// case 0: return (Object) target.a;
|
||||||
|
// case 1: return (Object) target.b;
|
||||||
|
// case 2: return (Object) target.c;
|
||||||
|
// case 3: return super.read(fieldIndex);
|
||||||
|
// case 4: return super.read(fieldIndex);
|
||||||
|
// case 5: return (Object) target.f;
|
||||||
|
// case 6: return (Object) target.g;
|
||||||
|
// case 7: return (Object) target.h;
|
||||||
|
// default:
|
||||||
|
// throw new FieldAccessException("Invalid index " + fieldIndex);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// protected StructureModifier<TField> writeGenerated(int index, Object value) throws FieldAccessException {
|
||||||
|
//
|
||||||
|
// Packet20NamedEntitySpawn target = typedTarget;
|
||||||
|
//
|
||||||
|
// switch (index) {
|
||||||
|
// case 0: target.a = (Integer) value; break;
|
||||||
|
// case 1: target.b = (String) value; break;
|
||||||
|
// case 2: target.c = (Integer) value; break;
|
||||||
|
// case 3: target.d = (Integer) value; break;
|
||||||
|
// case 4: super.write(index, value); break;
|
||||||
|
// case 5: super.write(index, value); break;
|
||||||
|
// case 6: target.g = (Byte) value; break;
|
||||||
|
// case 7: target.h = (Integer) value; break;
|
||||||
|
// default:
|
||||||
|
// throw new FieldAccessException("Invalid index " + index);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Chaining
|
||||||
|
// return this;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a StructureModifier compiler.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public final class StructureCompiler {
|
||||||
|
|
||||||
|
// Used to store generated classes of different types
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private class StructureKey {
|
||||||
|
private Class targetType;
|
||||||
|
private Class fieldType;
|
||||||
|
|
||||||
|
public StructureKey(Class targetType, Class fieldType) {
|
||||||
|
this.targetType = targetType;
|
||||||
|
this.fieldType = fieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(targetType, fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof StructureKey) {
|
||||||
|
StructureKey other = (StructureKey) obj;
|
||||||
|
return Objects.equal(targetType, other.targetType) &&
|
||||||
|
Objects.equal(fieldType, other.fieldType);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to load classes
|
||||||
|
private volatile static Method defineMethod;
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private Map<StructureKey, Class> compiledCache = new HashMap<StructureKey, Class>();
|
||||||
|
|
||||||
|
// The class loader we'll store our classes
|
||||||
|
private ClassLoader loader;
|
||||||
|
|
||||||
|
// References to other classes
|
||||||
|
private static String PACKAGE_NAME = "com/comphenix/protocol/reflect/compiler";
|
||||||
|
private static String SUPER_CLASS = "com/comphenix/protocol/reflect/StructureModifier";
|
||||||
|
private static String COMPILED_CLASS = PACKAGE_NAME + "/CompiledStructureModifier";
|
||||||
|
private static String FIELD_EXCEPTION_CLASS = "com/comphenix/protocol/reflect/FieldAccessException";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a structure compiler.
|
||||||
|
* @param loader - main class loader.
|
||||||
|
*/
|
||||||
|
StructureCompiler(ClassLoader loader) {
|
||||||
|
this.loader = loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles the given structure modifier.
|
||||||
|
* <p>
|
||||||
|
* WARNING: Do NOT call this method in the main thread. Compiling may easily take 10 ms, which is already
|
||||||
|
* over 1/4 of a tick (50 ms). Let the background thread automatically compile the structure modifiers instead.
|
||||||
|
* @param source - structure modifier to compile.
|
||||||
|
* @return A compiled structure modifier.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public synchronized <TField> StructureModifier<TField> compile(StructureModifier<TField> source) {
|
||||||
|
|
||||||
|
// We cannot optimize a structure modifier with no public fields
|
||||||
|
if (!isAnyPublic(source.getFields())) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
StructureKey key = new StructureKey(source.getTargetType(), source.getFieldType());
|
||||||
|
Class<?> compiledClass = compiledCache.get(key);
|
||||||
|
|
||||||
|
if (!compiledCache.containsKey(key)) {
|
||||||
|
compiledClass = generateClass(source);
|
||||||
|
compiledCache.put(key, compiledClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, create an instance of this class
|
||||||
|
try {
|
||||||
|
return (StructureModifier<TField>) compiledClass.getConstructor(
|
||||||
|
StructureModifier.class, StructureCompiler.class).
|
||||||
|
newInstance(source, this);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalStateException("Used invalid parameters in instance creation", e);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new RuntimeException("Security limitation!", e);
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Security limitation! Cannot create instance of dynamic class.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Error occured while instancing generated class.", e);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new IllegalStateException("Cannot happen.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <TField> Class<?> generateClass(StructureModifier<TField> source) {
|
||||||
|
|
||||||
|
ClassWriter cw = new ClassWriter(0);
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Class targetType = source.getTargetType();
|
||||||
|
|
||||||
|
String className = "CompiledStructure$" + targetType.getSimpleName() + source.getFieldType().getSimpleName();
|
||||||
|
String targetSignature = Type.getDescriptor(targetType);
|
||||||
|
String targetName = targetType.getName().replace('.', '/');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// This class might have been generated before. Try to load it.
|
||||||
|
Class<?> before = loader.loadClass(PACKAGE_NAME.replace('/', '.') + "." + className);
|
||||||
|
|
||||||
|
if (before != null)
|
||||||
|
return before;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// That's ok.
|
||||||
|
}
|
||||||
|
|
||||||
|
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
|
||||||
|
"<TField:Ljava/lang/Object;>L" + COMPILED_CLASS + "<TTField;>;",
|
||||||
|
COMPILED_CLASS, null);
|
||||||
|
|
||||||
|
createFields(cw, targetSignature);
|
||||||
|
createConstructor(cw, className, targetSignature, targetName);
|
||||||
|
createReadMethod(cw, className, source.getFields(), targetSignature, targetName);
|
||||||
|
createWriteMethod(cw, className, source.getFields(), targetSignature, targetName);
|
||||||
|
cw.visitEnd();
|
||||||
|
|
||||||
|
byte[] data = cw.toByteArray();
|
||||||
|
|
||||||
|
// Call the define method
|
||||||
|
try {
|
||||||
|
if (defineMethod == null) {
|
||||||
|
Method defined = ClassLoader.class.getDeclaredMethod("defineClass",
|
||||||
|
new Class<?>[] { String.class, byte[].class, int.class, int.class });
|
||||||
|
|
||||||
|
// Awesome. Now, create and return it.
|
||||||
|
defined.setAccessible(true);
|
||||||
|
defineMethod = defined;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Class clazz = (Class) defineMethod.invoke(loader, null, data, 0, data.length);
|
||||||
|
|
||||||
|
// DEBUG CODE: Print the content of the generated class.
|
||||||
|
//org.objectweb.asm.ClassReader cr = new org.objectweb.asm.ClassReader(data);
|
||||||
|
//cr.accept(new ASMifierClassVisitor(new PrintWriter(System.out)), 0);
|
||||||
|
|
||||||
|
return clazz;
|
||||||
|
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new RuntimeException("Cannot use reflection to dynamically load a class.", e);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new IllegalStateException("Incompatible JVM.", e);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new IllegalStateException("Cannot call defineMethod - wrong JVM?", e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new RuntimeException("Security limitation! Cannot dynamically load class.", e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new RuntimeException("Error occured in code generator.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if at least one of the given fields is public.
|
||||||
|
* @param fields - field to test.
|
||||||
|
* @return TRUE if one or more field is publically accessible, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
private boolean isAnyPublic(List<Field> fields) {
|
||||||
|
// Are any of the fields public?
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
if (isPublic(fields.get(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPublic(Field field) {
|
||||||
|
return Modifier.isPublic(field.getModifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createFields(ClassWriter cw, String targetSignature) {
|
||||||
|
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
|
||||||
|
typedField.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||||
|
|
||||||
|
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
|
||||||
|
String methodSignature = "(ITTField;)L" + SUPER_CLASS + "<TTField;>;";
|
||||||
|
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
|
||||||
|
new String[] { FIELD_EXCEPTION_CLASS });
|
||||||
|
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||||
|
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||||
|
mv.visitVarInsn(Opcodes.ASTORE, 3);
|
||||||
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
|
||||||
|
// The last label is for the default switch
|
||||||
|
Label[] labels = new Label[fields.size()];
|
||||||
|
Label errorLabel = new Label();
|
||||||
|
Label returnLabel = new Label();
|
||||||
|
|
||||||
|
// Generate labels
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
labels[i] = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitTableSwitchInsn(0, labels.length - 1, errorLabel, labels);
|
||||||
|
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
|
||||||
|
Class<?> outputType = fields.get(i).getType();
|
||||||
|
Class<?> inputType = PrimitiveUtils.wrap(outputType);
|
||||||
|
String typeDescriptor = Type.getDescriptor(outputType);
|
||||||
|
String inputPath = inputType.getName().replace('.', '/');
|
||||||
|
|
||||||
|
mv.visitLabel(labels[i]);
|
||||||
|
|
||||||
|
// Push the compare object
|
||||||
|
if (i == 0)
|
||||||
|
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
|
||||||
|
else
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
|
||||||
|
// Only write to public fields
|
||||||
|
if (isPublic(fields.get(i))) {
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 3);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
|
||||||
|
if (!PrimitiveUtils.isPrimitive(outputType))
|
||||||
|
mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
|
||||||
|
else
|
||||||
|
boxingHelper.unbox(Type.getType(outputType));
|
||||||
|
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Use reflection. We don't have a choice, unfortunately.
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";");
|
||||||
|
mv.visitInsn(Opcodes.POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitJumpInsn(Opcodes.GOTO, returnLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitLabel(errorLabel);
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
|
||||||
|
mv.visitInsn(Opcodes.DUP);
|
||||||
|
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||||
|
mv.visitInsn(Opcodes.DUP);
|
||||||
|
mv.visitLdcInsn("Invalid index ");
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
|
||||||
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
|
||||||
|
mv.visitInsn(Opcodes.ATHROW);
|
||||||
|
|
||||||
|
mv.visitLabel(returnLabel);
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitInsn(Opcodes.ARETURN);
|
||||||
|
mv.visitMaxs(5, 4);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
|
||||||
|
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
|
||||||
|
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
|
||||||
|
BoxingHelper boxingHelper = new BoxingHelper(mv);
|
||||||
|
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||||
|
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
||||||
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
|
||||||
|
// The last label is for the default switch
|
||||||
|
Label[] labels = new Label[fields.size()];
|
||||||
|
Label errorLabel = new Label();
|
||||||
|
|
||||||
|
// Generate labels
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
labels[i] = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
|
||||||
|
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
Class<?> outputType = fields.get(i).getType();
|
||||||
|
String typeDescriptor = Type.getDescriptor(outputType);
|
||||||
|
|
||||||
|
mv.visitLabel(labels[i]);
|
||||||
|
|
||||||
|
// Push the compare object
|
||||||
|
if (i == 0)
|
||||||
|
mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
|
||||||
|
else
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
|
||||||
|
// Note that byte code cannot access non-public fields
|
||||||
|
if (isPublic(fields.get(i))) {
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||||
|
|
||||||
|
boxingHelper.box(Type.getType(outputType));
|
||||||
|
} else {
|
||||||
|
// We have to use reflection for private and protected fields.
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;");
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitInsn(Opcodes.ARETURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv.visitLabel(errorLabel);
|
||||||
|
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||||
|
mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
|
||||||
|
mv.visitInsn(Opcodes.DUP);
|
||||||
|
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||||
|
mv.visitInsn(Opcodes.DUP);
|
||||||
|
mv.visitLdcInsn("Invalid index ");
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
|
||||||
|
mv.visitVarInsn(Opcodes.ILOAD, 1);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
|
||||||
|
mv.visitInsn(Opcodes.ATHROW);
|
||||||
|
mv.visitMaxs(5, 3);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
|
||||||
|
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
|
||||||
|
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
|
||||||
|
"(L" + SUPER_CLASS + "<TTField;>;L" + SUPER_CLASS + ";)V", null);
|
||||||
|
mv.visitCode();
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, PACKAGE_NAME + "/" + className, "initialize", "(L" + SUPER_CLASS + ";)V");
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||||
|
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(Opcodes.GETFIELD, PACKAGE_NAME + "/" + className, "target", "Ljava/lang/Object;");
|
||||||
|
mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "typedTarget", targetSignature);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
||||||
|
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||||
|
mv.visitFieldInsn(Opcodes.PUTFIELD, PACKAGE_NAME + "/" + className, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
|
||||||
|
mv.visitInsn(Opcodes.RETURN);
|
||||||
|
mv.visitMaxs(2, 3);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,8 @@ package com.comphenix.protocol.reflect.instances;
|
|||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
|
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
@ -100,6 +102,38 @@ public class DefaultInstances {
|
|||||||
return getDefaultInternal(type, registered, 0);
|
return getDefaultInternal(type, registered, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the constructor with the fewest number of parameters.
|
||||||
|
* @param type - type to construct.
|
||||||
|
* @return A constructor with the fewest number of parameters, or NULL if the type has no constructors.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> Constructor<T> getMinimumConstructor(Class<T> type) {
|
||||||
|
|
||||||
|
Constructor<T> minimum = null;
|
||||||
|
int lastCount = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
// Find the constructor with the fewest parameters
|
||||||
|
for (Constructor<?> candidate : type.getConstructors()) {
|
||||||
|
Class<?>[] types = candidate.getParameterTypes();
|
||||||
|
|
||||||
|
// Note that we don't allow recursive types - that is, types that
|
||||||
|
// require itself in the constructor.
|
||||||
|
if (types.length < lastCount) {
|
||||||
|
if (!contains(types, type)) {
|
||||||
|
minimum = (Constructor<T>) candidate;
|
||||||
|
lastCount = types.length;
|
||||||
|
|
||||||
|
// Don't loop again if we've already found the best possible constructor
|
||||||
|
if (lastCount == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return minimum;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a default instance or value that is assignable to this type.
|
* Retrieves a default instance or value that is assignable to this type.
|
||||||
* <p>
|
* <p>
|
||||||
@ -136,36 +170,18 @@ public class DefaultInstances {
|
|||||||
if (value != null)
|
if (value != null)
|
||||||
return (T) value;
|
return (T) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Constructor<T> minimum = null;
|
Constructor<T> minimum = getMinimumConstructor(type);
|
||||||
int lastCount = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
// Find the constructor with the fewest parameters
|
|
||||||
for (Constructor<?> candidate : type.getConstructors()) {
|
|
||||||
Class<?>[] types = candidate.getParameterTypes();
|
|
||||||
|
|
||||||
// Note that we don't allow recursive types - that is, types that
|
|
||||||
// require itself in the constructor.
|
|
||||||
if (types.length < lastCount) {
|
|
||||||
if (!contains(types, type)) {
|
|
||||||
minimum = (Constructor<T>) candidate;
|
|
||||||
lastCount = types.length;
|
|
||||||
|
|
||||||
// Don't loop again if we've already found the best possible constructor
|
|
||||||
if (lastCount == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the type with this constructor using default values. This might fail, though.
|
// Create the type with this constructor using default values. This might fail, though.
|
||||||
try {
|
try {
|
||||||
if (minimum != null) {
|
if (minimum != null) {
|
||||||
Object[] params = new Object[lastCount];
|
int parameterCount = minimum.getParameterTypes().length;
|
||||||
|
Object[] params = new Object[parameterCount];
|
||||||
Class<?>[] types = minimum.getParameterTypes();
|
Class<?>[] types = minimum.getParameterTypes();
|
||||||
|
|
||||||
// Fill out
|
// Fill out
|
||||||
for (int i = 0; i < lastCount; i++) {
|
for (int i = 0; i < parameterCount; i++) {
|
||||||
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
|
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +196,24 @@ public class DefaultInstances {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct default instances using the CGLIB enhancer object instead.
|
||||||
|
* @param enhancer - a CGLIB enhancer to use.
|
||||||
|
* @return A default instance generator that uses the CGLIB enhancer.
|
||||||
|
*/
|
||||||
|
public DefaultInstances forEnhancer(Enhancer enhancer) {
|
||||||
|
final Enhancer ex = enhancer;
|
||||||
|
|
||||||
|
return new DefaultInstances(registered) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
|
||||||
|
// Use the enhancer instead
|
||||||
|
return (T) ex.create(types, params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by the default instance provider to create a class from a given constructor.
|
* Used by the default instance provider to create a class from a given constructor.
|
||||||
* The default method uses reflection.
|
* The default method uses reflection.
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
package com.comphenix.protocol.reflect.instances;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides instance constructors using a list of existing values.
|
||||||
|
* <p>
|
||||||
|
* Only one instance per individual class.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class ExistingGenerator implements InstanceProvider {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private Map<Class, Object> existingValues = new HashMap<Class, Object>();
|
||||||
|
|
||||||
|
private ExistingGenerator() {
|
||||||
|
// Only accessible to the constructors
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically create an instance provider from a objects public and private fields.
|
||||||
|
* <p>
|
||||||
|
* If two or more fields share the same type, the last declared non-null field will take
|
||||||
|
* precedent.
|
||||||
|
* @param object - object to create an instance generator from.
|
||||||
|
* @return The instance generator.
|
||||||
|
*/
|
||||||
|
public static ExistingGenerator fromObjectFields(Object object) {
|
||||||
|
ExistingGenerator generator = new ExistingGenerator();
|
||||||
|
|
||||||
|
// Read instances from every field.
|
||||||
|
for (Field field : FuzzyReflection.fromObject(object, true).getFields()) {
|
||||||
|
try {
|
||||||
|
Object value = FieldUtils.readField(field, object, true);
|
||||||
|
|
||||||
|
// Use the type of the field, not the object itself
|
||||||
|
if (value != null)
|
||||||
|
generator.addObject(field.getType(), value);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Yes, swallow it. No, really.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance generator from a pre-defined array of values.
|
||||||
|
* @param values - values to provide.
|
||||||
|
* @return An instance provider that uses these values.
|
||||||
|
*/
|
||||||
|
public static ExistingGenerator fromObjectArray(Object[] values) {
|
||||||
|
ExistingGenerator generator = new ExistingGenerator();
|
||||||
|
|
||||||
|
for (Object value : values)
|
||||||
|
generator.addObject(value);
|
||||||
|
|
||||||
|
return generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addObject(Object value) {
|
||||||
|
if (value == null)
|
||||||
|
throw new IllegalArgumentException("Value cannot be NULL.");
|
||||||
|
|
||||||
|
existingValues.put(value.getClass(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addObject(Class<?> type, Object value) {
|
||||||
|
existingValues.put(type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object create(@Nullable Class<?> type) {
|
||||||
|
|
||||||
|
Object value = existingValues.get(type);
|
||||||
|
|
||||||
|
// NULL values indicate that the generator failed
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
name: ProtocolLib
|
name: ProtocolLib
|
||||||
version: 1.1.0
|
version: 1.2.0
|
||||||
description: Provides read/write access to the Minecraft protocol.
|
description: Provides read/write access to the Minecraft protocol.
|
||||||
author: Comphenix
|
author: Comphenix
|
||||||
website: http://www.comphenix.net/ProtocolLib
|
website: http://www.comphenix.net/ProtocolLib
|
||||||
|
@ -129,5 +129,3 @@ types. It's remarkably consistent across different versions.
|
|||||||
### Incompatiblity
|
### Incompatiblity
|
||||||
|
|
||||||
The following plugins (to be expanded) are not compatible with ProtocolLib:
|
The following plugins (to be expanded) are not compatible with ProtocolLib:
|
||||||
|
|
||||||
* TagAPI
|
|
In neuem Issue referenzieren
Einen Benutzer sperren