Archiviert
13
0

Merge branch 'master' into gh-pages

Dieser Commit ist enthalten in:
Kristian S. Stangeland 2012-10-01 04:59:42 +02:00
Commit 22cb77d78e
43 geänderte Dateien mit 4517 neuen und 374 gelöschten Zeilen

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -25,8 +25,10 @@ import org.bukkit.Server;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
public class ProtocolLibrary extends JavaPlugin {
@ -39,10 +41,19 @@ public class ProtocolLibrary extends JavaPlugin {
// Metrics and 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
public void onLoad() {
logger = getLoggerSafely();
protocolManager = new PacketFilterManager(getClassLoader(), logger);
protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger);
}
@Override
@ -50,6 +61,12 @@ public class ProtocolLibrary extends JavaPlugin {
Server server = getServer();
PluginManager manager = server.getPluginManager();
// Initialize background compiler
if (backgroundCompiler == null) {
backgroundCompiler = new BackgroundCompiler(getClassLoader());
BackgroundCompiler.setInstance(backgroundCompiler);
}
// Notify server managers of incompatible plugins
checkForIncompatibility(manager);
@ -59,6 +76,9 @@ public class ProtocolLibrary extends JavaPlugin {
// Inject our hook into already existing players
protocolManager.initializePlayers(server.getOnlinePlayers());
// Timeout
createAsyncTask(server);
// Try to enable statistics
try {
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) {
// Plugin authors: Notify me to remove these
String[] incompatiblePlugins = { "TagAPI" };
String[] incompatiblePlugins = {};
for (String plugin : incompatiblePlugins) {
if (manager.getPlugin(plugin) != null) {
@ -83,6 +126,19 @@ public class ProtocolLibrary extends JavaPlugin {
@Override
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 = null;
statistisc = null;

Datei anzeigen

@ -17,7 +17,6 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
@ -35,7 +34,7 @@ import com.google.common.collect.ImmutableSet;
* Represents an API for accessing the Minecraft protocol.
* @author Kristian
*/
public interface ProtocolManager {
public interface ProtocolManager extends PacketStream {
/**
* Retrieves a list of every registered packet listener.
@ -66,46 +65,6 @@ public interface ProtocolManager {
* @param plugin - the plugin to unload.
*/
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.
@ -163,4 +122,10 @@ public interface ProtocolManager {
* @return TRUE if it has, FALSE otherwise.
*/
public boolean isClosed();
/**
* Retrieve the current asyncronous packet manager.
* @return Asyncronous packet manager.
*/
public AsynchronousManager getAsynchronousManager();
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -62,7 +62,7 @@ public class ListeningWhitelist {
* @return TRUE if there are any packets, FALSE otherwise.
*/
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);
}
/**
* 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
public boolean equals(final Object obj){
if(obj instanceof ListeningWhitelist){

Datei anzeigen

@ -17,6 +17,12 @@
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.Method;
@ -40,13 +46,18 @@ import net.minecraft.server.Packet;
*
* @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
protected StructureModifier<Object> structureModifier;
protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
@ -54,6 +65,10 @@ public class PacketContainer {
// The getEntity method
private static Method getEntity;
// Support for serialization
private static Method writeMethod;
private static Method readMethod;
static {
try {
Class.forName("net.minecraft.server.WorldType");
@ -291,4 +306,60 @@ public class PacketContainer {
public int getID() {
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);
}
}
}

Datei anzeigen

@ -17,22 +17,30 @@
package com.comphenix.protocol.events;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.EventObject;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import com.comphenix.protocol.async.AsyncMarker;
public class PacketEvent extends EventObject implements Cancellable {
/**
* Automatically generated by Eclipse.
*/
private static final long serialVersionUID = -5360289379097430620L;
private transient Player player;
private PacketContainer packet;
private Player player;
private boolean serverPacket;
private boolean cancel;
private AsyncMarker asyncMarker;
private boolean asynchronous;
/**
* Use the static constructors to create instances of this event.
* @param source - the event source.
@ -47,6 +55,16 @@ public class PacketEvent extends EventObject implements Cancellable {
this.player = player;
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.
@ -70,6 +88,16 @@ public class PacketEvent extends EventObject implements Cancellable {
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.
* @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.
* <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.
*/
public boolean isServerPacket() {
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();
}
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -35,8 +35,39 @@ class EntityUtilities {
private static Method hashGetMethod;
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 {
World world = entity.getWorld();
Object worldServer = ((CraftWorld) world).getHandle();

Datei anzeigen

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

Datei anzeigen

@ -49,7 +49,7 @@ class MinecraftRegistry {
// Initialize it, if we haven't already
if (packetToID == null) {
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);
} catch (IllegalAccessException e) {
@ -88,8 +88,8 @@ class MinecraftRegistry {
Map<Integer, Class> lookup = forceVanilla ? previousValues : overwrittenPackets;
// Optimized lookup
if (lookup.containsKey(packetToID)) {
return lookup.get(packetToID);
if (lookup.containsKey(packetID)) {
return lookup.get(packetID);
}
// Will most likely not be used

Datei anzeigen

@ -10,6 +10,9 @@ import java.util.concurrent.ConcurrentHashMap;
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.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
@ -48,7 +51,7 @@ class NetworkFieldInjector extends PlayerInjector {
}
@Override
protected void initialize() throws IllegalAccessException {
protected synchronized void initialize() throws IllegalAccessException {
super.initialize();
// 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
public void injectManager() {

Datei anzeigen

@ -11,6 +11,10 @@ import java.util.Set;
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.
*
@ -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
public void injectManager() {

Datei anzeigen

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

Datei anzeigen

@ -34,6 +34,7 @@ import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@ -45,7 +46,10 @@ import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
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.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
@ -60,14 +64,23 @@ public final class PacketFilterManager implements ProtocolManager {
*/
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,
/**
* 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
@ -84,26 +97,35 @@ public final class PacketFilterManager implements ProtocolManager {
// Packet injection
private PacketInjector packetInjector;
// Server connection injection
private InjectedServerConnection serverInjection;
// Enabled packet filters
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
// The two listener containers
private ConcurrentListenerMultimap recievedListeners = new ConcurrentListenerMultimap();
private ConcurrentListenerMultimap sendingListeners = new ConcurrentListenerMultimap();
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
private SortedPacketListenerList sendingListeners = new SortedPacketListenerList();
// Whether or not this class has been closed
private boolean hasClosed;
private volatile boolean hasClosed;
// The default class loader
private ClassLoader classLoader;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Error logger
private Logger logger;
// The async packet handler
private AsyncFilterManager asyncFilterManager;
/**
* 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)
throw new IllegalArgumentException("logger cannot be NULL.");
if (classLoader == null)
@ -114,11 +136,18 @@ public final class PacketFilterManager implements ProtocolManager {
this.classLoader = classLoader;
this.logger = logger;
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup);
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
this.serverInjection = new InjectedServerConnection(logger, server);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
}
}
@Override
public AsynchronousManager getAsynchronousManager() {
return asyncFilterManager;
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
@ -133,6 +162,13 @@ public final class PacketFilterManager implements ProtocolManager {
*/
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.playerHook = playerHook;
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : packetListeners) {
checkListener(listener);
}
}
}
public Logger getLogger() {
@ -161,12 +197,17 @@ public final class PacketFilterManager implements ProtocolManager {
if (hasSending || hasReceiving) {
// Add listeners and hooks
if (hasSending) {
verifyWhitelist(listener, sending);
sendingListeners.addListener(listener, sending);
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
}
if (hasReceiving) {
verifyWhitelist(listener, receiving);
recievedListeners.addListener(listener, receiving);
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
// We don't know if we've hooked any players yet
checkListener(listener);
}
// 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
public void removePacketListener(PacketListener listener) {
if (listener == null)
@ -212,6 +283,9 @@ public final class PacketFilterManager implements ProtocolManager {
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.
*/
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.
*/
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);
Packet mcPacket = packet.getHandle();
// Make sure the packet isn't cancelled
packetInjector.undoCancel(packet.getID(), mcPacket);
if (filters) {
mcPacket = injector.handlePacketRecieved(mcPacket);
}
@ -343,8 +454,6 @@ public final class PacketFilterManager implements ProtocolManager {
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
EntityUtilities.updateEntity(entity, observers);
}
@ -360,20 +469,22 @@ public final class PacketFilterManager implements ProtocolManager {
/**
* Used to construct a player hook.
* @param player - the player to hook.
* @param hook - the hook type.
* @return A new player hoook
* @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
switch (playerHook) {
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(player, this, sendingFilters);
case NETWORK_MANAGER_OBJECT:
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.
*/
protected void injectPlayer(Player player) {
PlayerInjector injector = null;
PlayerInjectHooks currentHook = playerHook;
boolean firstPlayer = lastSuccessfulHook == null;
// Don't inject if the class has closed
if (!hasClosed && player != null && !playerInjection.containsKey(player)) {
try {
PlayerInjector injector = getPlayerHookInstance(player);
while (true) {
try {
injector = getHookInstance(player, currentHook);
injector.injectManager();
playerInjection.put(player, injector);
connectionLookup.put(injector.getInputStream(false), player);
break;
} catch (Exception e) {
injector.injectManager();
playerInjection.put(player, injector);
connectionLookup.put(injector.getInputStream(false), player);
} catch (IllegalAccessException e) {
// Mark this injection attempt as a failure
playerInjection.put(player, null);
logger.log(Level.SEVERE, "Unable to access fields.", e);
// Mark this injection attempt as a failure
logger.log(Level.SEVERE, "Player hook " + currentHook.toString() + " failed.", e);
// Clean up as much as possible
try {
if (injector != null)
injector.cleanupAll();
} 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)
injectPlayer(((PlayerJoinEvent) event).getPlayer());
else if (event instanceof PlayerQuitEvent)
injectPlayer(((PlayerQuitEvent) event).getPlayer());
uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
}
return null;
}
@ -524,12 +666,12 @@ public final class PacketFilterManager implements ProtocolManager {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.get(player);
DataInputStream input = injector.getInputStream(true);
if (injector != null) {
DataInputStream input = injector.getInputStream(true);
injector.cleanupAll();
playerInjection.remove(injector);
playerInjection.remove(player);
connectionLookup.remove(input);
}
}
@ -565,23 +707,31 @@ public final class PacketFilterManager implements ProtocolManager {
public void close() {
// Guard
if (hasClosed)
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
injection.cleanupAll();
if (injection != null) {
injection.cleanupAll();
}
}
// Remove packet handlers
if (packetInjector != null)
packetInjector.cleanupAll();
// Remove server handler
serverInjection.cleanupAll();
hasClosed = true;
// Remove listeners
packetListeners.clear();
playerInjection.clear();
connectionLookup.clear();
hasClosed = true;
// Clean up async handlers. We have to do this last.
asyncFilterManager.cleanupAll();
}
@Override

Datei anzeigen

@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Player;
@ -52,6 +53,9 @@ class PacketInjector {
// Allows us to determine the sender
private Map<DataInputStream, Player> playerLookup;
// Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier;
// Class loader
private ClassLoader classLoader;
@ -61,9 +65,24 @@ class PacketInjector {
this.classLoader = classLoader;
this.manager = manager;
this.playerLookup = playerLookup;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
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 {
if (intHashMap == null) {
// 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);
Class proxy = ex.createClass();
// Create the proxy handler
ReadPacketModifier modifier = new ReadPacketModifier(packetID, this);
readModifier.put(packetID, modifier);
// Add a static reference
Enhancer.registerStaticCallbacks(proxy, new Callback[] {
new ReadPacketModifier(packetID, this)
});
Enhancer.registerStaticCallbacks(proxy, new Callback[] { modifier });
try {
// Override values
@ -147,6 +168,7 @@ class PacketInjector {
putMethod.invoke(intHashMap, packetID, old);
previous.remove(packetID);
readModifier.remove(packetID);
registry.remove(proxy);
overwritten.remove(packetID);
return true;

Datei anzeigen

@ -31,6 +31,7 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
@ -56,9 +57,11 @@ abstract class PlayerInjector {
// Reference to the player's network manager
protected VolatileField networkManagerRef;
protected VolatileField serverHandlerRef;
protected Object networkManager;
// Current net handler
protected Object serverHandler;
protected Object netHandler;
// The packet manager and filters
@ -75,12 +78,18 @@ abstract class PlayerInjector {
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 {
CraftPlayer craft = (CraftPlayer) player;
EntityPlayer notchEntity = craft.getHandle();
Object serverHandler = null;
EntityPlayer notchEntity = getEntityPlayer();
if (!hasInitialized) {
// Do this first, in case we encounter an exception
@ -89,7 +98,8 @@ abstract class PlayerInjector {
// Retrieve the server handler
if (serverHandlerField == null)
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
serverHandler = FieldUtils.readField(serverHandlerField, notchEntity);
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue();
// Next, get the network manager
if (networkManagerField == null)
@ -118,26 +128,31 @@ abstract class PlayerInjector {
* @return Current net handler.
* @throws IllegalAccessException Unable to find or retrieve net handler.
*/
private Object getNetHandler() throws IllegalAccessException {
protected Object getNetHandler() throws IllegalAccessException {
// What a mess
try {
if (netHandlerField == null)
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType("net\\.minecraft\\.NetHandler");
} catch (RuntimeException e1) {
// Swallow it
}
// Second attempt
if (netHandlerField == null) {
try {
// Well, that sucks. Try just Minecraft objects then.
netHandlerField = FuzzyReflection.fromClass(networkManagerField.getType(), true).
netHandlerField = FuzzyReflection.fromClass(networkManager.getClass(), true).
getFieldByType(FuzzyReflection.MINECRAFT_OBJECT);
} catch (RuntimeException e2) {
return new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
throw new IllegalAccessException("Cannot locate net handler. " + e2.getMessage());
}
}
// Get the handler
if (netHandler != null)
if (netHandler == null)
netHandler = FieldUtils.readField(netHandlerField, networkManager, true);
return netHandler;
}
@ -190,6 +205,14 @@ abstract class PlayerInjector {
*/
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.
* @param packet - packet to recieve.
@ -200,7 +223,7 @@ abstract class PlayerInjector {
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
// Make sure we're listening
if (sendingFilters.contains(id)) {
if (id != null && sendingFilters.contains(id)) {
// A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(manager, container, player);

Datei anzeigen

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

Datei anzeigen

@ -45,6 +45,23 @@ class ReadPacketModifier implements MethodInterceptor {
this.packetID = packetID;
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
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)) {
Object overridenObject = override.get(thisObj);
// Cancel EVERYTHING, including "processPacket"
if (overridenObject == null)
return null;
// This packet has been cancelled
if (overridenObject == 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);
} else {
@ -75,7 +97,7 @@ class ReadPacketModifier implements MethodInterceptor {
// We need this in order to get the correct player
DataInputStream input = (DataInputStream) args[0];
// Let the people know
PacketContainer container = new PacketContainer(packetID, (Packet) thisObj);
PacketEvent event = packetInjector.packetRecieved(container, input);

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -17,12 +17,17 @@
package com.comphenix.protocol.injector;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.server.Packet;
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.
@ -30,7 +35,10 @@ import com.comphenix.protocol.reflect.StructureModifier;
*/
public class StructureCache {
// 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.
@ -53,15 +61,51 @@ public class StructureCache {
* @return A structure modifier.
*/
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);
// Use the vanilla class definition
// We don't want to create this for every lookup
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);
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;

Datei anzeigen

@ -19,8 +19,10 @@ package com.comphenix.protocol.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
@ -96,6 +98,7 @@ public class FuzzyReflection {
* Retrieves a method by looking at its name.
* @param nameRegex - regular expression that will match method names.
* @return The first method that satisfies the regular expression.
* @throws RuntimeException If the method cannot be found.
*/
public Method getMethodByName(String nameRegex) {
@ -139,11 +142,38 @@ public class FuzzyReflection {
* @return The first method that satisfies the parameter types.
*/
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
for (Method method : getMethods()) {
if (method.getReturnType().equals(returnType) && Arrays.equals(method.getParameterTypes(), args)) {
return method;
if (match.matcher(method.getReturnType().getName()).matches()) {
if (matchParameters(argMatch, method.getParameterTypes()))
return method;
}
}
@ -151,6 +181,39 @@ public class FuzzyReflection {
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.
* @param nameRegex - regular expression that will match a field name.
@ -172,6 +235,46 @@ public class FuzzyReflection {
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.
* <p>

Datei anzeigen

@ -21,34 +21,45 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
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.google.common.base.Function;
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")
public class StructureModifier<TField> {
// Object and its type
private Class targetType;
private Object target;
protected Class targetType;
protected Object target;
// Converter. May be NULL.
private EquivalentConverter<TField> converter;
protected EquivalentConverter<TField> converter;
// The fields to read in order
private Class fieldType;
private List<Field> data = new ArrayList<Field>();
protected Class fieldType;
protected List<Field> data = new ArrayList<Field>();
// Improved default values
private Set<Field> defaultFields;
protected Map<Field, Integer> defaultFields;
// 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.
@ -58,22 +69,38 @@ public class StructureModifier<TField> {
*/
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
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);
this.target = target;
/**
* Consumers of this method should call "initialize".
*/
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) {
this.targetType = targetType;
this.fieldType = fieldType;
@ -101,7 +128,7 @@ public class StructureModifier<TField> {
Object result = FieldUtils.readField(data.get(fieldIndex), target, true);
// Use the converter, if we have it
if (converter != null)
if (needConversion())
return converter.getSpecific(result);
else
return (TField) result;
@ -140,7 +167,7 @@ public class StructureModifier<TField> {
throw new IllegalStateException("Cannot write to a NULL target.");
// Use the converter, if it exists
Object obj = converter != null ? converter.getGeneric(value) : value;
Object obj = needConversion() ? converter.getGeneric(value) : value;
try {
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
@ -152,6 +179,14 @@ public class StructureModifier<TField> {
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.
* @param fieldIndex - index of the potential field.
@ -197,7 +232,7 @@ public class StructureModifier<TField> {
DefaultInstances generator = DefaultInstances.DEFAULT;
// Write a default instance to every field
for (Field field : defaultFields) {
for (Field field : defaultFields.keySet()) {
try {
FieldUtils.writeField(field, target,
generator.getDefault(field.getType()), true);
@ -223,24 +258,32 @@ public class StructureModifier<TField> {
// Do we need to update the cache?
if (result == null) {
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) {
if (fieldType != null && fieldType.isAssignableFrom(field.getType())) {
filtered.add(field);
if (defaultFields.contains(field))
defaults.add(field);
// Don't use the original index
if (defaultFields.containsKey(field))
defaults.put(field, index);
}
// Keep track of the field index
index++;
}
// Cache structure modifiers
result = new StructureModifier<T>();
result.initialize(targetType, fieldType, filtered, defaults,
converter, new HashMap<Class, StructureModifier>());
result = withFieldType(fieldType, filtered, defaults, converter);
if (fieldType != null)
if (fieldType != null) {
subtypeCache.put(fieldType, result);
// Automatically compile the structure modifier
if (BackgroundCompiler.getInstance() != null)
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
}
}
// Add the target too
@ -296,28 +339,59 @@ public class StructureModifier<TField> {
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.
* @param target - different target of the same type.
* @return Structure modifier with the new 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.
* @param converter- the object converter to use.
* @param converter - the object converter to use.
* @return Structure modifier with the new converter.
*/
@SuppressWarnings("unchecked")
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;
}
/**
* 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.
* @return List of fields.
@ -325,7 +399,7 @@ public class StructureModifier<TField> {
public List<Field> getFields() {
return ImmutableList.copyOf(data);
}
/**
* Retrieve every value stored in the fields of the current type.
* @return Every field value.
@ -334,17 +408,19 @@ public class StructureModifier<TField> {
public List<TField> getValues() throws FieldAccessException {
List<TField> values = new ArrayList<TField>();
for (int i = 0; i < size(); i++)
for (int i = 0; i < size(); i++) {
values.add(read(i));
}
return 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;
int index = 0;
for (Field field : fields) {
Class<?> type = field.getType();
@ -354,9 +430,12 @@ public class StructureModifier<TField> {
// Next, see if we actually can generate a default value
if (generator.getDefault(type) != null) {
// If so, require it
requireDefaults.add(field);
requireDefaults.put(field, index);
}
}
// Increment field index
index++;
}
return requireDefaults;

Datei anzeigen

@ -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() {
// Load the value if we haven't already
if (!previousLoaded) {

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -20,6 +20,8 @@ package com.comphenix.protocol.reflect.instances;
import java.lang.reflect.Constructor;
import java.util.*;
import net.sf.cglib.proxy.Enhancer;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
@ -100,6 +102,38 @@ public class DefaultInstances {
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.
* <p>
@ -136,36 +170,18 @@ public class DefaultInstances {
if (value != null)
return (T) value;
}
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;
}
}
}
Constructor<T> minimum = getMinimumConstructor(type);
// Create the type with this constructor using default values. This might fail, though.
try {
if (minimum != null) {
Object[] params = new Object[lastCount];
int parameterCount = minimum.getParameterTypes().length;
Object[] params = new Object[parameterCount];
Class<?>[] types = minimum.getParameterTypes();
// Fill out
for (int i = 0; i < lastCount; i++) {
for (int i = 0; i < parameterCount; i++) {
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
}
@ -180,6 +196,24 @@ public class DefaultInstances {
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.
* The default method uses reflection.

Datei anzeigen

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

Datei anzeigen

@ -1,5 +1,5 @@
name: ProtocolLib
version: 1.1.0
version: 1.2.0
description: Provides read/write access to the Minecraft protocol.
author: Comphenix
website: http://www.comphenix.net/ProtocolLib

Datei anzeigen

@ -129,5 +129,3 @@ types. It's remarkably consistent across different versions.
### Incompatiblity
The following plugins (to be expanded) are not compatible with ProtocolLib:
* TagAPI