Archiviert
13
0

Merge branch 'master' into gh-pages

Dieser Commit ist enthalten in:
Kristian S. Stangeland 2012-10-05 04:40:27 +02:00
Commit f56cf3290e
27 geänderte Dateien mit 1717 neuen und 528 gelöschten Zeilen

Datei anzeigen

@ -20,7 +20,6 @@ public interface AsynchronousManager {
* Registers an asynchronous packet handler. * Registers an asynchronous packet handler.
* <p> * <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread. * 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. * @param listener - the packet listener that will recieve these asynchronous events.
* @return An asynchrouns handler. * @return An asynchrouns handler.
*/ */

Datei anzeigen

@ -53,6 +53,7 @@ public class AsyncFilterManager implements AsynchronousManager {
// Server packets are synchronized already // Server packets are synchronized already
this.serverQueue = new PacketSendingQueue(false); this.serverQueue = new PacketSendingQueue(false);
// Client packets must be synchronized // Client packets must be synchronized
this.clientQueue = new PacketSendingQueue(true); this.clientQueue = new PacketSendingQueue(true);
@ -68,28 +69,40 @@ public class AsyncFilterManager implements AsynchronousManager {
@Override @Override
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) { public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
return registerAsyncHandler(listener, true);
}
/**
* Registers an asynchronous packet handler.
* <p>
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
* <p>
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
* If you already have a synchronous event, call this method with autoInject set to FALSE.
*
* @param listener - the packet listener that will recieve these asynchronous events.
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
* @return An asynchrouns handler.
*/
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener); AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist(); ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist(); 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 // Add listener to either or both processing queue
if (hasValidWhitelist(sendingWhitelist)) { if (hasValidWhitelist(sendingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist); PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
serverProcessingQueue.addListener(handler, sendingWhitelist); serverProcessingQueue.addListener(handler, sendingWhitelist);
hasListener &= hasPacketListener(sendingWhitelist);
} }
if (hasValidWhitelist(receivingWhitelist)) { if (hasValidWhitelist(receivingWhitelist)) {
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist); PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
clientProcessingQueue.addListener(handler, receivingWhitelist); clientProcessingQueue.addListener(handler, receivingWhitelist);
hasListener &= hasPacketListener(receivingWhitelist);
} }
if (!hasListener) { // We need a synchronized listener to get the ball rolling
if (autoInject) {
handler.setNullPacketListener(new NullPacketListener(listener)); handler.setNullPacketListener(new NullPacketListener(listener));
manager.addPacketListener(handler.getNullPacketListener()); manager.addPacketListener(handler.getNullPacketListener());
} }
@ -97,15 +110,6 @@ public class AsyncFilterManager implements AsynchronousManager {
return handler; 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) { private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
return whitelist != null && whitelist.getWhitelist().size() > 0; return whitelist != null && whitelist.getWhitelist().size() > 0;
} }

Datei anzeigen

@ -1,6 +1,10 @@
package com.comphenix.protocol.async; package com.comphenix.protocol.async;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -17,18 +21,26 @@ import com.comphenix.protocol.events.PacketListener;
public class AsyncListenerHandler { public class AsyncListenerHandler {
/** /**
* Signal an end to the packet processing. * Signal an end to packet processing.
*/ */
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object()); private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
/**
* Called when the threads have to wake up for something important.
*/
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
// Unique worker ID
private static final AtomicInteger nextID = new AtomicInteger();
// Default queue capacity // Default queue capacity
private static int DEFAULT_CAPACITY = 1024; private static int DEFAULT_CAPACITY = 1024;
// Cancel the async handler // Cancel the async handler
private volatile boolean cancelled; private volatile boolean cancelled;
// If we've started the listener loop before // Number of worker threads
private volatile boolean started; private final AtomicInteger started = new AtomicInteger();
// The packet listener // The packet listener
private PacketListener listener; private PacketListener listener;
@ -40,6 +52,10 @@ public class AsyncListenerHandler {
// List of queued packets // List of queued packets
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY); private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
// List of cancelled tasks
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
private final Object stopLock = new Object();
// Minecraft main thread // Minecraft main thread
private Thread mainThread; private Thread mainThread;
@ -78,16 +94,24 @@ public class AsyncListenerHandler {
return nullPacketListener; return nullPacketListener;
} }
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;
}
/** /**
* Cancel the handler. * Cancel the handler.
*/ */
public void cancel() { public void cancel() {
// Remove the listener as quickly as possible // Remove the listener as quickly as possible
close(); close();
// Poison Pill Shutdown
queuedPackets.clear();
queuedPackets.add(INTERUPT_PACKET);
} }
/** /**
@ -103,46 +127,208 @@ public class AsyncListenerHandler {
} }
/** /**
* Create a runnable that will initiate the listener loop. * Create a worker that will initiate the listener loop. Note that using stop() to
* close a specific worker is less efficient than stopping an arbitrary worker.
* <p> * <p>
* <b>Warning</b>: Never call the run() method in the main thread. * <b>Warning</b>: Never call the run() method in the main thread.
*/ */
public Runnable getListenerLoop() { public AsyncRunnable getListenerLoop() {
return new Runnable() { return new AsyncRunnable() {
private final AtomicBoolean firstRun = new AtomicBoolean();
private final AtomicBoolean finished = new AtomicBoolean();
private final int id = nextID.incrementAndGet();
@Override
public int getID() {
return id;
}
@Override @Override
public void run() { public void run() {
listenerLoop(); // Careful now
if (firstRun.compareAndSet(false, true)) {
listenerLoop(id);
synchronized (stopLock) {
stoppedTasks.remove(id);
stopLock.notifyAll();
finished.set(true);
}
} else {
if (finished.get())
throw new IllegalStateException(
"This listener has already been run. Create a new instead.");
else
throw new IllegalStateException(
"This listener loop has already been started. Create a new instead.");
}
}
@Override
public boolean stop() throws InterruptedException {
synchronized (stopLock) {
if (!isRunning())
return false;
stoppedTasks.add(id);
// Wake up threads - we have a listener to stop
for (int i = 0; i < getWorkers(); i++) {
queuedPackets.offer(WAKEUP_PACKET);
}
finished.set(true);
waitForStops();
return true;
}
}
@Override
public boolean isRunning() {
return firstRun.get() && !finished.get();
}
@Override
public boolean isFinished() {
return finished.get();
} }
}; };
} }
/**
* Start a singler worker thread handling the asynchronous.
*/
public synchronized void start() {
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
if (cancelled)
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
}
/**
* Start multiple worker threads for this listener.
* @param count - number of worker threads to start.
*/
public synchronized void start(int count) {
for (int i = 0; i < count; i++)
start();
}
/**
* Stop a worker thread.
*/
public synchronized void stop() {
queuedPackets.add(INTERUPT_PACKET);
}
/**
* Stop the given amount of worker threads.
* @param count - number of threads to stop.
*/
public synchronized void stop(int count) {
for (int i = 0; i < count; i++)
stop();
}
/**
* Set the current number of workers.
* <p>
* This method can only be called with a count of zero when the listener is closing.
* @param count - new number of workers.
*/
public synchronized void setWorkers(int count) {
if (count < 0)
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
if (count > DEFAULT_CAPACITY)
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
if (cancelled && count > 0)
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
long time = System.currentTimeMillis();
// Try to get to the correct count
while (started.get() != count) {
if (started.get() < count)
start();
else
stop();
// May happen if another thread is doing something similar to "setWorkers"
if ((System.currentTimeMillis() - time) > 1000)
throw new RuntimeException("Failed to set worker count.");
}
}
/**
* Retrieve the current number of registered workers.
* <p>
* Note that the returned value may be out of data.
* @return Number of registered workers.
*/
public synchronized int getWorkers() {
return started.get();
}
/**
* Wait until every tasks scheduled to stop has actually stopped.
* @return TRUE if the current listener should stop, FALSE otherwise.
* @throws InterruptedException - If the current thread was interrupted.
*/
private boolean waitForStops() throws InterruptedException {
synchronized (stopLock) {
while (stoppedTasks.size() > 0 && !cancelled) {
stopLock.wait();
}
return cancelled;
}
}
// DO NOT call this method from the main thread // DO NOT call this method from the main thread
private void listenerLoop() { private void listenerLoop(int workerID) {
// Danger, danger! // Danger, danger!
if (Thread.currentThread().getId() == mainThread.getId()) if (Thread.currentThread().getId() == mainThread.getId())
throw new IllegalStateException("Do not call this method from the main thread."); 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) if (cancelled)
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead."); throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
// Proceed
started = true;
try { try {
// Wait if certain threads are stopping
if (waitForStops())
return;
// Proceed
started.incrementAndGet();
mainLoop: mainLoop:
while (!cancelled) { while (!cancelled) {
PacketEvent packet = queuedPackets.take(); PacketEvent packet = queuedPackets.take();
AsyncMarker marker = packet.getAsyncMarker(); AsyncMarker marker = packet.getAsyncMarker();
// Handle cancel requests // Handle cancel requests
if (packet == null || marker == null || !packet.isAsynchronous()) { if (packet == null || marker == null || packet == INTERUPT_PACKET) {
break; return;
} else if (packet == WAKEUP_PACKET) {
// This is a bit slow, but it should be safe
synchronized (stopLock) {
// Are we the one who is supposed to stop?
if (stoppedTasks.contains(workerID))
return;
if (waitForStops())
return;
}
} }
// Here's the core of the asynchronous processing // Here's the core of the asynchronous processing
try { try {
marker.setListenerHandler(this);
marker.setWorkerID(workerID);
if (packet.isServerPacket()) if (packet.isServerPacket())
listener.onPacketSending(packet); listener.onPacketSending(packet);
else else
@ -171,40 +357,35 @@ public class AsyncListenerHandler {
} catch (InterruptedException e) { } catch (InterruptedException e) {
// We're done // We're done
} finally {
// Clean up
started.decrementAndGet();
close();
} }
// Clean up
close();
} }
private void close() { private synchronized void close() {
// Remove the listener itself // Remove the listener itself
if (!cancelled) { if (!cancelled) {
filterManager.unregisterAsyncHandlerInternal(this); filterManager.unregisterAsyncHandlerInternal(this);
cancelled = true; cancelled = true;
started = false;
// Tell every uncancelled thread to end
stopThreads();
} }
} }
private String getPluginName() {
return PacketAdapter.getPluginName(listener);
}
/** /**
* Retrieve the plugin associated with this async listener. * Use the poision pill method to stop every worker thread.
* @return The plugin.
*/ */
public Plugin getPlugin() { private void stopThreads() {
return listener != null ? listener.getPlugin() : null; // Poison Pill Shutdown
} queuedPackets.clear();
stop(started.get());
/** // Individual shut down is irrelevant now
* Start the asynchronous listener using the Bukkit scheduler. synchronized (stopLock) {
*/ stopLock.notifyAll();
public void start() { }
if (listener.getPlugin() == null)
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
} }
} }

Datei anzeigen

@ -67,6 +67,10 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
// Whether or not the asynchronous processing itself should be cancelled // Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled; private volatile boolean asyncCancelled;
// Used to identify the asynchronous worker
private AsyncListenerHandler listenerHandler;
private int workerID;
// Determine if Minecraft processes this packet asynchronously // Determine if Minecraft processes this packet asynchronously
private static Method isMinecraftAsync; private static Method isMinecraftAsync;
private static boolean alwaysSync; private static boolean alwaysSync;
@ -215,11 +219,47 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
this.asyncCancelled = asyncCancelled; this.asyncCancelled = asyncCancelled;
} }
/**
* Retrieve the current asynchronous listener handler.
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
*/
public AsyncListenerHandler getListenerHandler() {
return listenerHandler;
}
/**
* Set the current asynchronous listener handler.
* <p>
* Used by the worker to update the value.
* @param listenerHandler - new listener handler.
*/
void setListenerHandler(AsyncListenerHandler listenerHandler) {
this.listenerHandler = listenerHandler;
}
/**
* Retrieve the current worker ID.
* @return Current worker ID.
*/
public int getWorkerID() {
return workerID;
}
/**
* Set the current worker ID.
* <p>
* Used by the worker.
* @param workerID - new worker ID.
*/
void setWorkerID(int workerID) {
this.workerID = workerID;
}
/** /**
* Retrieve iterator for the next listener in line. * Retrieve iterator for the next listener in line.
* @return Next async packet listener iterator. * @return Next async packet listener iterator.
*/ */
public Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() { Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
return listenerTraversal; return listenerTraversal;
} }

Datei anzeigen

@ -0,0 +1,35 @@
package com.comphenix.protocol.async;
/**
* A runnable representing a asynchronous event listener.
*
* @author Kristian
*/
public interface AsyncRunnable extends Runnable {
/**
* Retrieve a unique worker ID.
* @return Unique worker ID.
*/
public int getID();
/**
* Stop the given runnable.
* <p>
* This may not occur right away.
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
*/
public boolean stop() throws InterruptedException;
/**
* Determine if we're running or not.
* @return TRUE if we're running, FALSE otherwise.
*/
public boolean isRunning();
/**
* Determine if this runnable has already run its course.
* @return TRUE if it has been stopped, FALSE otherwise.
*/
boolean isFinished();
}

Datei anzeigen

@ -0,0 +1,40 @@
package com.comphenix.protocol.async;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain;
/**
* Provides a comparable to a packet event.
*
* @author Kristian
*/
class PacketEventHolder implements Comparable<PacketEventHolder> {
private PacketEvent event;
/**
* A wrapper that ensures the packet event is ordered by sending index.
* @param event - packet event to wrap.
*/
public PacketEventHolder(PacketEvent event) {
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
}
/**
* Retrieve the stored event.
* @return The stored event.
*/
public PacketEvent getEvent() {
return event;
}
@Override
public int compareTo(PacketEventHolder other) {
AsyncMarker marker = other != null ? other.getEvent().getAsyncMarker() : null;
return ComparisonChain.start().
compare(event.getAsyncMarker(), marker).
result();
}
}

Datei anzeigen

@ -2,12 +2,14 @@ package com.comphenix.protocol.async;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue; import java.util.Queue;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PrioritizedListener; import com.comphenix.protocol.injector.PrioritizedListener;
import com.google.common.collect.MinMaxPriorityQueue;
/** /**
* Handles the processing of every packet type. * Handles the processing of every packet type.
@ -16,6 +18,9 @@ import com.comphenix.protocol.injector.PrioritizedListener;
*/ */
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> { class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
// Initial number of elements
public static final int INITIAL_CAPACITY = 64;
/** /**
* Default maximum number of packets to process concurrently. * Default maximum number of packets to process concurrently.
*/ */
@ -33,18 +38,23 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
private Semaphore concurrentProcessing; private Semaphore concurrentProcessing;
// Queued packets for being processed // Queued packets for being processed
private ArrayBlockingQueue<PacketEvent> processingQueue; private Queue<PacketEventHolder> processingQueue;
// Packets for sending // Packets for sending
private PacketSendingQueue sendingQueue; private PacketSendingQueue sendingQueue;
public PacketProcessingQueue(PacketSendingQueue sendingQueue) { public PacketProcessingQueue(PacketSendingQueue sendingQueue) {
this(sendingQueue, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY); this(sendingQueue, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
} }
public PacketProcessingQueue(PacketSendingQueue sendingQueue, int queueLimit, int maximumConcurrency) { public PacketProcessingQueue(PacketSendingQueue sendingQueue, int initialSize, int maximumSize, int maximumConcurrency) {
super(); super();
this.processingQueue = new ArrayBlockingQueue<PacketEvent>(queueLimit);
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
expectedSize(initialSize).
maximumSize(maximumSize).
<PacketEventHolder>create(), null);
this.maximumConcurrency = maximumConcurrency; this.maximumConcurrency = maximumConcurrency;
this.concurrentProcessing = new Semaphore(maximumConcurrency); this.concurrentProcessing = new Semaphore(maximumConcurrency);
this.sendingQueue = sendingQueue; this.sendingQueue = sendingQueue;
@ -58,7 +68,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
*/ */
public boolean enqueue(PacketEvent packet, boolean onMainThread) { public boolean enqueue(PacketEvent packet, boolean onMainThread) {
try { try {
processingQueue.add(packet); processingQueue.add(new PacketEventHolder(packet));
// Begin processing packets // Begin processing packets
signalBeginProcessing(onMainThread); signalBeginProcessing(onMainThread);
@ -68,18 +78,27 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
} }
} }
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return processingQueue.size();
}
/** /**
* Called by the current method and each thread to signal that a packet might be ready for processing. * 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. * @param onMainThread - whether or not this is occuring on the main thread.
*/ */
public void signalBeginProcessing(boolean onMainThread) { public void signalBeginProcessing(boolean onMainThread) {
while (concurrentProcessing.tryAcquire()) { while (concurrentProcessing.tryAcquire()) {
PacketEvent packet = processingQueue.poll(); PacketEventHolder holder = processingQueue.poll();
// Any packet queued? // Any packet queued?
if (packet != null) { if (holder != null) {
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID()); PacketEvent packet = holder.getEvent();
AsyncMarker marker = packet.getAsyncMarker(); AsyncMarker marker = packet.getAsyncMarker();
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
// Yes, removing the marker will cause the chain to stop // Yes, removing the marker will cause the chain to stop
if (list != null) { if (list != null) {

Datei anzeigen

@ -1,15 +1,15 @@
package com.comphenix.protocol.async; package com.comphenix.protocol.async;
import java.io.IOException; import java.io.IOException;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.PriorityBlockingQueue;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.google.common.collect.ComparisonChain;
/** /**
* Represents packets ready to be transmitted to a client. * Represents packets ready to be transmitted to a client.
@ -17,28 +17,28 @@ import com.google.common.collect.ComparisonChain;
*/ */
class PacketSendingQueue { class PacketSendingQueue {
private static final int INITIAL_CAPACITY = 64; public static final int INITIAL_CAPACITY = 64;
private PriorityBlockingQueue<PacketEvent> sendingQueue; private PriorityBlockingQueue<PacketEventHolder> sendingQueue;
// Whether or not packet transmission can only occur on the main thread // Whether or not packet transmission can only occur on the main thread
private final boolean synchronizeMain; private final boolean synchronizeMain;
/**
* Number of packet events in the queue.
* @return The number of packet events in the queue.
*/
public int size() {
return sendingQueue.size();
}
/** /**
* Create a packet sending queue. * Create a packet sending queue.
* @param synchronizeMain - whether or not to synchronize with the main thread. * @param synchronizeMain - whether or not to synchronize with the main thread.
*/ */
public PacketSendingQueue(boolean synchronizeMain) { public PacketSendingQueue(boolean synchronizeMain) {
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
this.synchronizeMain = 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();
}
});
} }
/** /**
@ -46,7 +46,7 @@ class PacketSendingQueue {
* @param packet * @param packet
*/ */
public void enqueue(PacketEvent packet) { public void enqueue(PacketEvent packet) {
sendingQueue.add(packet); sendingQueue.add(new PacketEventHolder(packet));
} }
/** /**
@ -70,7 +70,9 @@ class PacketSendingQueue {
Set<Integer> lookup = new HashSet<Integer>(packetsRemoved); Set<Integer> lookup = new HashSet<Integer>(packetsRemoved);
// Note that this is O(n), so it might be expensive // Note that this is O(n), so it might be expensive
for (PacketEvent event : sendingQueue) { for (PacketEventHolder holder : sendingQueue) {
PacketEvent event = holder.getEvent();
if (lookup.contains(event.getPacketID())) { if (lookup.contains(event.getPacketID())) {
event.getAsyncMarker().setProcessed(true); event.getAsyncMarker().setProcessed(true);
} }
@ -88,9 +90,10 @@ class PacketSendingQueue {
// Transmit as many packets as we can // Transmit as many packets as we can
while (true) { while (true) {
PacketEvent current = sendingQueue.peek(); PacketEventHolder holder = sendingQueue.peek();
if (current != null) { if (holder != null) {
PacketEvent current = holder.getEvent();
AsyncMarker marker = current.getAsyncMarker(); AsyncMarker marker = current.getAsyncMarker();
// Abort if we're not on the main thread // Abort if we're not on the main thread
@ -111,7 +114,10 @@ class PacketSendingQueue {
if (marker.isProcessed() || marker.hasExpired()) { if (marker.isProcessed() || marker.hasExpired()) {
if (marker.isProcessed() && !current.isCancelled()) { if (marker.isProcessed() && !current.isCancelled()) {
sendPacket(current); // Silently skip players that have logged out
if (isOnline(current.getPlayer())) {
sendPacket(current);
}
} }
sendingQueue.poll(); sendingQueue.poll();
@ -124,15 +130,19 @@ class PacketSendingQueue {
} }
} }
private boolean isOnline(Player player) {
return player != null && player.isOnline();
}
/** /**
* Send every packet, regardless of the processing state. * Send every packet, regardless of the processing state.
*/ */
private void forceSend() { private void forceSend() {
while (true) { while (true) {
PacketEvent current = sendingQueue.poll(); PacketEventHolder holder = sendingQueue.poll();
if (current != null) { if (holder != null) {
sendPacket(current); sendPacket(holder.getEvent());
} else { } else {
break; break;
} }

Datei anzeigen

@ -0,0 +1,211 @@
package com.comphenix.protocol.async;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Queue;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
/**
* Synchronization views copied from Google Guava.
*
* @author Kristian
*/
class Synchronization {
/**
* Create a synchronized wrapper for the given queue.
* <p>
* This wrapper cannot synchronize the iterator(). Callers are expected
* to synchronize iterators manually.
* @param queue - the queue to synchronize.
* @param mutex - synchronization mutex, or NULL to use the queue.
* @return A synchronization wrapper.
*/
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
return (queue instanceof SynchronizedQueue) ?
queue :
new SynchronizedQueue<E>(queue, mutex);
}
private static class SynchronizedObject implements Serializable {
private static final long serialVersionUID = -4408866092364554628L;
final Object delegate;
final Object mutex;
SynchronizedObject(Object delegate, @Nullable Object mutex) {
this.delegate = Preconditions.checkNotNull(delegate);
this.mutex = (mutex == null) ? this : mutex;
}
Object delegate() {
return delegate;
}
// No equals and hashCode; see ForwardingObject for details.
@Override
public String toString() {
synchronized (mutex) {
return delegate.toString();
}
}
}
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
private static final long serialVersionUID = 5440572373531285692L;
private SynchronizedCollection(Collection<E> delegate,
@Nullable Object mutex) {
super(delegate, mutex);
}
@SuppressWarnings("unchecked")
@Override
Collection<E> delegate() {
return (Collection<E>) super.delegate();
}
@Override
public boolean add(E e) {
synchronized (mutex) {
return delegate().add(e);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
synchronized (mutex) {
return delegate().addAll(c);
}
}
@Override
public void clear() {
synchronized (mutex) {
delegate().clear();
}
}
@Override
public boolean contains(Object o) {
synchronized (mutex) {
return delegate().contains(o);
}
}
@Override
public boolean containsAll(Collection<?> c) {
synchronized (mutex) {
return delegate().containsAll(c);
}
}
@Override
public boolean isEmpty() {
synchronized (mutex) {
return delegate().isEmpty();
}
}
@Override
public Iterator<E> iterator() {
return delegate().iterator(); // manually synchronized
}
@Override
public boolean remove(Object o) {
synchronized (mutex) {
return delegate().remove(o);
}
}
@Override
public boolean removeAll(Collection<?> c) {
synchronized (mutex) {
return delegate().removeAll(c);
}
}
@Override
public boolean retainAll(Collection<?> c) {
synchronized (mutex) {
return delegate().retainAll(c);
}
}
@Override
public int size() {
synchronized (mutex) {
return delegate().size();
}
}
@Override
public Object[] toArray() {
synchronized (mutex) {
return delegate().toArray();
}
}
@Override
public <T> T[] toArray(T[] a) {
synchronized (mutex) {
return delegate().toArray(a);
}
}
}
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
private static final long serialVersionUID = 1961791630386791902L;
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
super(delegate, mutex);
}
@Override
Queue<E> delegate() {
return (Queue<E>) super.delegate();
}
@Override
public E element() {
synchronized (mutex) {
return delegate().element();
}
}
@Override
public boolean offer(E e) {
synchronized (mutex) {
return delegate().offer(e);
}
}
@Override
public E peek() {
synchronized (mutex) {
return delegate().peek();
}
}
@Override
public E poll() {
synchronized (mutex) {
return delegate().poll();
}
}
@Override
public E remove() {
synchronized (mutex) {
return delegate().remove();
}
}
}
}

Datei anzeigen

@ -0,0 +1,27 @@
package com.comphenix.protocol.injector;
import net.minecraft.server.Packet;
import com.comphenix.protocol.events.PacketEvent;
public interface ListenerInvoker {
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketRecieving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public abstract void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated ID of a packet.
* @param packet - the packet.
* @return The packet ID.
*/
public abstract int getPacketID(Packet packet);
}

Datei anzeigen

@ -1,181 +0,0 @@
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

@ -17,13 +17,10 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
@ -51,12 +48,13 @@ import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.async.AsyncMarker;
import com.comphenix.protocol.events.*; import com.comphenix.protocol.events.*;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
public final class PacketFilterManager implements ProtocolManager { public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
/** /**
* Sets the inject hook type. Different types allow for maximum compatibility. * Sets the inject hook type. Different types allow for maximum compatibility.
@ -87,21 +85,11 @@ public final class PacketFilterManager implements ProtocolManager {
private Set<PacketListener> packetListeners = private Set<PacketListener> packetListeners =
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>()); Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
// Player injection
private Map<DataInputStream, Player> connectionLookup = new ConcurrentHashMap<DataInputStream, Player>();
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
// Player injection type
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_HANDLER_FIELDS;
// Packet injection // Packet injection
private PacketInjector packetInjector; private PacketInjector packetInjector;
// Server connection injection // Player injection
private InjectedServerConnection serverInjection; private PlayerInjectionHandler playerInjection;
// Enabled packet filters
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
// The two listener containers // The two listener containers
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList(); private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
@ -113,9 +101,6 @@ public final class PacketFilterManager implements ProtocolManager {
// The default class loader // The default class loader
private ClassLoader classLoader; private ClassLoader classLoader;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Error logger // Error logger
private Logger logger; private Logger logger;
@ -135,9 +120,9 @@ public final class PacketFilterManager implements ProtocolManager {
// Initialize values // Initialize values
this.classLoader = classLoader; this.classLoader = classLoader;
this.logger = logger; this.logger = logger;
this.packetInjector = new PacketInjector(classLoader, this, connectionLookup); this.playerInjection = new PlayerInjectionHandler(classLoader, logger, this, server);
this.packetInjector = new PacketInjector(classLoader, this, playerInjection);
this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this);
this.serverInjection = new InjectedServerConnection(logger, server);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); logger.log(Level.SEVERE, "Unable to initialize packet injector.", e);
} }
@ -153,7 +138,7 @@ public final class PacketFilterManager implements ProtocolManager {
* @return Injection method for reading server packets. * @return Injection method for reading server packets.
*/ */
public PlayerInjectHooks getPlayerHook() { public PlayerInjectHooks getPlayerHook() {
return playerHook; return playerInjection.getPlayerHook();
} }
/** /**
@ -161,14 +146,10 @@ public final class PacketFilterManager implements ProtocolManager {
* @param playerHook - the new injection method for reading server packets. * @param playerHook - the new injection method for reading server packets.
*/ */
public void setPlayerHook(PlayerInjectHooks playerHook) { public void setPlayerHook(PlayerInjectHooks playerHook) {
this.playerHook = playerHook; playerInjection.setPlayerHook(playerHook);
// Make sure the current listeners are compatible // Make sure the current listeners are compatible
if (lastSuccessfulHook != null) { playerInjection.checkListener(packetListeners);
for (PacketListener listener : packetListeners) {
checkListener(listener);
}
}
} }
public Logger getLogger() { public Logger getLogger() {
@ -200,14 +181,14 @@ public final class PacketFilterManager implements ProtocolManager {
verifyWhitelist(listener, sending); verifyWhitelist(listener, sending);
sendingListeners.addListener(listener, sending); sendingListeners.addListener(listener, sending);
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist()); enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
// Make sure this is possible
playerInjection.checkListener(listener);
} }
if (hasReceiving) { if (hasReceiving) {
verifyWhitelist(listener, receiving); verifyWhitelist(listener, receiving);
recievedListeners.addListener(listener, receiving); recievedListeners.addListener(listener, receiving);
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
// We don't know if we've hooked any players yet
checkListener(listener);
} }
// Inform our injected hooks // Inform our injected hooks
@ -231,20 +212,6 @@ public final class PacketFilterManager implements ProtocolManager {
} }
} }
/**
* Determine if a listener is valid or not.
* @param listener - listener to check.
* @throws IllegalStateException If the given listener's whitelist cannot be fulfilled.
*/
public void checkListener(PacketListener listener) {
try {
if (lastSuccessfulHook != null)
lastSuccessfulHook.checkListener(listener);
} catch (Exception e) {
throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e);
}
}
@Override @Override
public void removePacketListener(PacketListener listener) { public void removePacketListener(PacketListener listener) {
if (listener == null) if (listener == null)
@ -288,18 +255,12 @@ public final class PacketFilterManager implements ProtocolManager {
asyncFilterManager.unregisterAsyncHandlers(plugin); asyncFilterManager.unregisterAsyncHandlers(plugin);
} }
/** @Override
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public void invokePacketRecieving(PacketEvent event) { public void invokePacketRecieving(PacketEvent event) {
handlePacket(recievedListeners, event, false); handlePacket(recievedListeners, event, false);
} }
/** @Override
* Invokes the given packet event for every registered listener.
* @param event - the packet event to invoke.
*/
public void invokePacketSending(PacketEvent event) { public void invokePacketSending(PacketEvent event) {
handlePacket(sendingListeners, event, true); handlePacket(sendingListeners, event, true);
} }
@ -352,7 +313,7 @@ public final class PacketFilterManager implements ProtocolManager {
for (int packetID : packets) { for (int packetID : packets) {
if (side.isForServer()) if (side.isForServer())
sendingFilters.add(packetID); playerInjection.addPacketHandler(packetID);
if (side.isForClient() && packetInjector != null) if (side.isForClient() && packetInjector != null)
packetInjector.addPacketHandler(packetID); packetInjector.addPacketHandler(packetID);
} }
@ -369,7 +330,7 @@ public final class PacketFilterManager implements ProtocolManager {
for (int packetID : packets) { for (int packetID : packets) {
if (side.isForServer()) if (side.isForServer())
sendingFilters.remove(packetID); playerInjection.removePacketHandler(packetID);
if (side.isForClient() && packetInjector != null) if (side.isForClient() && packetInjector != null)
packetInjector.removePacketHandler(packetID); packetInjector.removePacketHandler(packetID);
} }
@ -387,7 +348,7 @@ public final class PacketFilterManager implements ProtocolManager {
if (packet == null) if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL."); throw new IllegalArgumentException("packet cannot be NULL.");
getInjector(reciever).sendServerPacket(packet.getHandle(), filters); playerInjection.sendServerPacket(reciever, packet, filters);
} }
@Override @Override
@ -403,17 +364,21 @@ public final class PacketFilterManager implements ProtocolManager {
if (packet == null) if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL."); throw new IllegalArgumentException("packet cannot be NULL.");
PlayerInjector injector = getInjector(sender);
Packet mcPacket = packet.getHandle(); Packet mcPacket = packet.getHandle();
// Make sure the packet isn't cancelled // Make sure the packet isn't cancelled
packetInjector.undoCancel(packet.getID(), mcPacket); packetInjector.undoCancel(packet.getID(), mcPacket);
if (filters) { if (filters) {
mcPacket = injector.handlePacketRecieved(mcPacket); PacketEvent event = packetInjector.packetRecieved(packet, sender);
if (!event.isCancelled())
mcPacket = event.getPacket().getHandle();
else
return;
} }
injector.processPacket(mcPacket); playerInjection.processPacket(sender, mcPacket);
} }
@Override @Override
@ -444,7 +409,7 @@ public final class PacketFilterManager implements ProtocolManager {
@Override @Override
public Set<Integer> getSendingFilters() { public Set<Integer> getSendingFilters() {
return ImmutableSet.copyOf(sendingFilters); return playerInjection.getSendingFilters();
} }
@Override @Override
@ -463,81 +428,7 @@ public final class PacketFilterManager implements ProtocolManager {
*/ */
public void initializePlayers(Player[] players) { public void initializePlayers(Player[] players) {
for (Player player : players) for (Player player : players)
injectPlayer(player); playerInjection.injectPlayer(player);
}
/**
* 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 getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
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.");
}
}
/**
* Initialize a player hook, allowing us to read server packets.
* @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)) {
while (true) {
try {
injector = getHookInstance(player, currentHook);
injector.injectManager();
playerInjection.put(player, injector);
connectionLookup.put(injector.getInputStream(false), player);
break;
} catch (Exception 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);
}
} }
/** /**
@ -550,17 +441,17 @@ public final class PacketFilterManager implements ProtocolManager {
try { try {
manager.registerEvents(new Listener() { manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
injectPlayer(event.getPlayer()); playerInjection.injectPlayer(event.getPlayer());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
uninjectPlayer(event.getPlayer()); playerInjection.uninjectPlayer(event.getPlayer());
} }
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPluginDisabled(PluginDisableEvent event) { public void onPluginDisabled(PluginDisableEvent event) {
// Clean up in case the plugin forgets // Clean up in case the plugin forgets
if (event.getPlugin() != plugin) { if (event.getPlugin() != plugin) {
@ -576,6 +467,14 @@ public final class PacketFilterManager implements ProtocolManager {
} }
} }
@Override
public int getPacketID(Packet packet) {
if (packet == null)
throw new IllegalArgumentException("Packet cannot be NULL.");
return MinecraftRegistry.getPacketToID().get(packet.getClass());
}
// Yes, this is crazy. // Yes, this is crazy.
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
private void registerOld(PluginManager manager, Plugin plugin) { private void registerOld(PluginManager manager, Plugin plugin) {
@ -588,7 +487,7 @@ public final class PacketFilterManager implements ProtocolManager {
Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority"); Class eventPriority = loader.loadClass("org.bukkit.event.Event$Priority");
// Get the priority // Get the priority
Object priorityNormal = Enum.valueOf(eventPriority, "Normal"); Object priorityNormal = Enum.valueOf(eventPriority, "Highest");
// Get event types // Get event types
Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN"); Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
@ -617,9 +516,9 @@ public final class PacketFilterManager implements ProtocolManager {
// Check for the correct event // Check for the correct event
if (event instanceof PlayerJoinEvent) if (event instanceof PlayerJoinEvent)
injectPlayer(((PlayerJoinEvent) event).getPlayer()); playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer());
else if (event instanceof PlayerQuitEvent) else if (event instanceof PlayerQuitEvent)
uninjectPlayer(((PlayerQuitEvent) event).getPlayer()); playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
} }
return null; return null;
} }
@ -662,36 +561,6 @@ public final class PacketFilterManager implements ProtocolManager {
} }
} }
private void uninjectPlayer(Player player) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.get(player);
if (injector != null) {
DataInputStream input = injector.getInputStream(true);
injector.cleanupAll();
playerInjection.remove(player);
connectionLookup.remove(input);
}
}
}
private PlayerInjector getInjector(Player player) {
if (!playerInjection.containsKey(player)) {
// What? Try to inject again.
injectPlayer(player);
}
PlayerInjector injector = playerInjection.get(player);
// Check that the injector was sucessfully added
if (injector != null)
return injector;
else
throw new IllegalArgumentException("Player has no injected handler.");
}
/** /**
* Retrieves the current plugin class loader. * Retrieves the current plugin class loader.
* @return Class loader. * @return Class loader.
@ -707,28 +576,19 @@ public final class PacketFilterManager implements ProtocolManager {
public void close() { public void close() {
// Guard // Guard
if (hasClosed || playerInjection == null) if (hasClosed)
return; return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove packet handlers // Remove packet handlers
if (packetInjector != null) if (packetInjector != null)
packetInjector.cleanupAll(); packetInjector.cleanupAll();
// Remove server handler // Remove server handler
serverInjection.cleanupAll(); playerInjection.close();
hasClosed = true; hasClosed = true;
// Remove listeners // Remove listeners
packetListeners.clear(); packetListeners.clear();
playerInjection.clear();
connectionLookup.clear();
// Clean up async handlers. We have to do this last. // Clean up async handlers. We have to do this last.
asyncFilterManager.cleanupAll(); asyncFilterManager.cleanupAll();

Datei anzeigen

@ -33,6 +33,7 @@ import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
@ -48,10 +49,10 @@ class PacketInjector {
private static Object intHashMap; private static Object intHashMap;
// The packet filter manager // The packet filter manager
private PacketFilterManager manager; private ListenerInvoker manager;
// Allows us to determine the sender // Allows us to determine the sender
private Map<DataInputStream, Player> playerLookup; private PlayerInjectionHandler playerInjection;
// Allows us to look up read packet injectors // Allows us to look up read packet injectors
private Map<Integer, ReadPacketModifier> readModifier; private Map<Integer, ReadPacketModifier> readModifier;
@ -59,12 +60,12 @@ class PacketInjector {
// Class loader // Class loader
private ClassLoader classLoader; private ClassLoader classLoader;
public PacketInjector(ClassLoader classLoader, PacketFilterManager manager, public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
Map<DataInputStream, Player> playerLookup) throws IllegalAccessException { PlayerInjectionHandler playerInjection) throws IllegalAccessException {
this.classLoader = classLoader; this.classLoader = classLoader;
this.manager = manager; this.manager = manager;
this.playerLookup = playerLookup; this.playerInjection = playerInjection;
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>(); this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
initialize(); initialize();
} }
@ -194,7 +195,18 @@ class PacketInjector {
// Called from the ReadPacketModified monitor // Called from the ReadPacketModified monitor
PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) { PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
Player client = playerLookup.get(input); Player client = playerInjection.getPlayerByConnection(input);
return packetRecieved(packet, client);
}
/**
* Let the packet listeners process the given packet.
* @param packet - a packet to process.
* @param client - the client that sent the packet.
* @return The resulting packet event.
*/
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client); PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
manager.invokePacketRecieving(event); manager.invokePacketRecieving(event);

Datei anzeigen

@ -0,0 +1,41 @@
package com.comphenix.protocol.injector;
/**
* Invoked when attempting to use a player that has already logged out.
*
* @author Kristian
*/
public class PlayerLoggedOutException extends RuntimeException {
/**
* Generated by Eclipse.
*/
private static final long serialVersionUID = 4889257862160145234L;
public PlayerLoggedOutException() {
// Default error message
super("Cannot inject a player that has already logged out.");
}
public PlayerLoggedOutException(String message, Throwable cause) {
super(message, cause);
}
public PlayerLoggedOutException(String message) {
super(message);
}
public PlayerLoggedOutException(Throwable cause) {
super(cause);
}
/**
* Construct an exception from a formatted message.
* @param message - the message to format.
* @param params - parameters.
* @return The formated exception
*/
public static PlayerLoggedOutException fromFormat(String message, Object... params) {
return new PlayerLoggedOutException(String.format(message, params));
}
}

Datei anzeigen

@ -1,17 +1,17 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
import net.minecraft.server.Packet; import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodProxy;
import com.comphenix.protocol.injector.NetworkFieldInjector.FakePacket;
/** /**
* The array list that notifies when packets are sent by the server. * The array list that notifies when packets are sent by the server.
* *

Datei anzeigen

@ -1,4 +1,4 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -7,10 +7,13 @@ import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Server; import org.bukkit.Server;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.VolatileField;
/** /**
@ -134,18 +137,33 @@ class InjectedServerConnection {
if (list instanceof ReplacedArrayList) { if (list instanceof ReplacedArrayList) {
replacedLists.add((ReplacedArrayList<Object>) list); replacedLists.add((ReplacedArrayList<Object>) list);
} else { } else {
replacedLists.add(new ReplacedArrayList<Object>(list)); replacedLists.add(createReplacement(list));
listFieldRef.setValue(replacedLists.get(0)); listFieldRef.setValue(replacedLists.get(0));
listFields.add(listFieldRef); listFields.add(listFieldRef);
} }
} }
// Hack to avoid the "moved to quickly" error
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
return new ReplacedArrayList<Object>(list) {
@Override
protected void onReplacing(Object inserting, Object replacement) {
// Is this a normal Minecraft object?
if (!(inserting instanceof Factory)) {
// If so, copy the content of the old element to the new
ObjectCloner.copyTo(inserting, replacement, inserting.getClass());
}
}
};
}
/** /**
* Replace the server handler instance kept by the "keep alive" object. * Replace the server handler instance kept by the "keep alive" object.
* @param oldHandler - old server handler. * @param oldHandler - old server handler.
* @param newHandler - new, proxied server handler. * @param newHandler - new, proxied server handler.
*/ */
public void replaceServerHandler(Object oldHandler, Object newHandler) { public void replaceServerHandler(Object oldHandler, Object newHandler) {
if (!hasAttempted) { if (!hasAttempted) {
injectList(); injectList();
} }

Datei anzeigen

@ -1,4 +1,4 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.player;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -7,12 +7,14 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
@ -46,12 +48,27 @@ class NetworkFieldInjector extends PlayerInjector {
private static Field syncField; private static Field syncField;
private Object syncObject; private Object syncObject;
public NetworkFieldInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException { // Determine if we're listening
super(player, manager, sendingFilters); private Set<Integer> sendingFilters;
// Used to construct proxy objects
private ClassLoader classLoader;
public NetworkFieldInjector(ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker manager, Set<Integer> sendingFilters) throws IllegalAccessException {
super(logger, player, manager);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
} }
@Override @Override
protected synchronized void initialize() throws IllegalAccessException { protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public synchronized void initialize() throws IllegalAccessException {
super.initialize(); super.initialize();
// Get the sync field as well // Get the sync field as well
@ -112,7 +129,7 @@ class NetworkFieldInjector extends PlayerInjector {
synchronized(syncObject) { synchronized(syncObject) {
// The list we'll be inserting // The list we'll be inserting
List<Packet> hackedList = new InjectedArrayList(manager.getClassLoader(), this, ignoredPackets); List<Packet> hackedList = new InjectedArrayList(classLoader, this, ignoredPackets);
// Add every previously stored packet // Add every previously stored packet
for (Packet packet : minecraftList) { for (Packet packet : minecraftList) {
@ -154,4 +171,9 @@ class NetworkFieldInjector extends PlayerInjector {
} }
overridenLists.clear(); overridenLists.clear();
} }
@Override
public boolean canInject() {
return true;
}
} }

Datei anzeigen

@ -1,4 +1,4 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -8,12 +8,14 @@ import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.Packets;
import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
/** /**
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method. * Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
@ -21,8 +23,17 @@ import com.comphenix.protocol.events.PacketListener;
* @author Kristian * @author Kristian
*/ */
class NetworkObjectInjector extends PlayerInjector { class NetworkObjectInjector extends PlayerInjector {
public NetworkObjectInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException { // Determine if we're listening
super(player, manager, sendingFilters); private Set<Integer> sendingFilters;
public NetworkObjectInjector(Logger logger, Player player, ListenerInvoker invoker, Set<Integer> sendingFilters) throws IllegalAccessException {
super(logger, player, invoker);
this.sendingFilters = sendingFilters;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
} }
@Override @Override
@ -107,4 +118,9 @@ class NetworkObjectInjector extends PlayerInjector {
// Clean up // Clean up
networkManagerRef.revertValue(); networkManagerRef.revertValue();
} }
@Override
public boolean canInject() {
return true;
}
} }

Datei anzeigen

@ -0,0 +1,249 @@
package com.comphenix.protocol.injector.player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
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 net.sf.cglib.proxy.NoOp;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.ObjectCloner;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.ExistingGenerator;
/**
* Represents a player hook into the NetServerHandler class.
*
* @author Kristian
*/
public class NetworkServerInjector extends PlayerInjector {
private static Method sendPacketMethod;
private InjectedServerConnection serverInjection;
// Determine if we're listening
private Set<Integer> sendingFilters;
// Used to create proxy objects
private ClassLoader classLoader;
public NetworkServerInjector(
ClassLoader classLoader, Logger logger, Player player,
ListenerInvoker invoker, Set<Integer> sendingFilters,
InjectedServerConnection serverInjection) throws IllegalAccessException {
super(logger, player, invoker);
this.classLoader = classLoader;
this.sendingFilters = sendingFilters;
this.serverInjection = serverInjection;
}
@Override
protected boolean hasListener(int packetID) {
return sendingFilters.contains(packetID);
}
@Override
public void initialize() throws IllegalAccessException {
super.initialize();
// Get the send packet method!
if (hasInitialized) {
if (sendPacketMethod == null)
sendPacketMethod = FuzzyReflection.fromObject(serverHandler).getMethodByName("sendPacket.*");
}
}
@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;
if (!tryInjectManager()) {
// Try to override the proxied object
if (proxyServerField != null) {
serverHandlerRef = new VolatileField(proxyServerField, serverHandler, true);
serverHandler = serverHandlerRef.getValue();
if (serverHandler == null)
throw new RuntimeException("Cannot hook player: Inner proxy object is NULL.");
// Try again
if (tryInjectManager()) {
// It worked - probably
return;
}
}
throw new RuntimeException(
"Cannot hook player: Unable to find a valid constructor for the NetServerHandler object.");
}
}
private boolean tryInjectManager() {
Class<?> serverClass = serverHandler.getClass();
Enhancer ex = new Enhancer();
Callback sendPacketCallback = new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
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);
};
};
Callback noOpCallback = NoOp.INSTANCE;
ex.setClassLoader(classLoader);
ex.setSuperclass(serverClass);
ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback });
ex.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.equals(sendPacketMethod))
return 0;
else
return 1;
}
});
// Find the Minecraft NetServerHandler superclass
Class<?> minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass());
ExistingGenerator generator = ExistingGenerator.fromObjectFields(serverHandler, minecraftSuperClass);
DefaultInstances serverInstances = null;
// Maybe the proxy instance can help?
Object proxyInstance = getProxyServerHandler();
// Use the existing server proxy when we create one
if (proxyInstance != null && proxyInstance != serverHandler) {
serverInstances = DefaultInstances.fromArray(generator,
ExistingGenerator.fromObjectArray(new Object[] { proxyInstance }));
} else {
serverInstances = DefaultInstances.fromArray(generator);
}
serverInstances.setNonNull(true);
serverInstances.setMaximumRecursion(1);
Object proxyObject = serverInstances.forEnhancer(ex).getDefault(serverClass);
// Inject it now
if (proxyObject != null) {
// This will be done by InjectedServerConnection instead
//copyTo(serverHandler, proxyObject);
serverInjection.replaceServerHandler(serverHandler, proxyObject);
serverHandlerRef.setValue(proxyObject);
return true;
} else {
return false;
}
}
private Object getProxyServerHandler() {
if (proxyServerField != null && !proxyServerField.equals(serverHandlerRef.getField())) {
try {
return FieldUtils.readField(proxyServerField, serverHandler, true);
} catch (Throwable e) {
// Oh well
}
}
return null;
}
private Class<?> getFirstMinecraftSuperClass(Class<?> clazz) {
if (clazz.getName().startsWith("net.minecraft.server."))
return clazz;
else if (clazz.equals(Object.class))
return clazz;
else
return getFirstMinecraftSuperClass(clazz.getSuperclass());
}
@Override
public void cleanupAll() {
if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) {
ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass());
serverHandlerRef.revertValue();
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();
}
}
serverInjection.revertServerHandler(serverHandler);
}
@Override
public void checkListener(PacketListener listener) {
// We support everything
}
@Override
public boolean canInject() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,311 @@
package com.comphenix.protocol.injector.player;
import java.io.DataInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.Packet;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
import com.google.common.collect.ImmutableSet;
/**
* Responsible for injecting into a player's sendPacket method.
*
* @author Kristian
*/
public class PlayerInjectionHandler {
// Server connection injection
private InjectedServerConnection serverInjection;
// The last successful player hook
private PlayerInjector lastSuccessfulHook;
// Player injection
private Map<DataInputStream, Player> connectionLookup = new ConcurrentHashMap<DataInputStream, Player>();
private Map<Player, PlayerInjector> playerInjection = new HashMap<Player, PlayerInjector>();
// Player injection type
private PlayerInjectHooks playerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
// Error logger
private Logger logger;
// Whether or not we're closing
private boolean hasClosed;
// Used to invoke events
private ListenerInvoker invoker;
// Enabled packet filters
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
// The class loader we're using
private ClassLoader classLoader;
public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, ListenerInvoker invoker, Server server) {
this.classLoader = classLoader;
this.logger = logger;
this.invoker = invoker;
this.serverInjection = new InjectedServerConnection(logger, server);
}
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
public PlayerInjectHooks getPlayerHook() {
return playerHook;
}
/**
* Add an underlying packet handler of the given ID.
* @param packetID - packet ID to register.
*/
public void addPacketHandler(int packetID) {
sendingFilters.add(packetID);
}
/**
* Remove an underlying packet handler of ths ID.
* @param packetID - packet ID to unregister.
*/
public void removePacketHandler(int packetID) {
sendingFilters.remove(packetID);
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
public void setPlayerHook(PlayerInjectHooks playerHook) {
this.playerHook = playerHook;
}
/**
* 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.
*/
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
// Construct the correct player hook
switch (hook) {
case NETWORK_HANDLER_FIELDS:
return new NetworkFieldInjector(classLoader, logger, player, invoker, sendingFilters);
case NETWORK_MANAGER_OBJECT:
return new NetworkObjectInjector(logger, player, invoker, sendingFilters);
case NETWORK_SERVER_OBJECT:
return new NetworkServerInjector(classLoader, logger, player, invoker, sendingFilters, serverInjection);
default:
throw new IllegalArgumentException("Cannot construct a player injector.");
}
}
public Player getPlayerByConnection(DataInputStream inputStream) {
return connectionLookup.get(inputStream);
}
/**
* Initialize a player hook, allowing us to read server packets.
* @param manager - the main packet filter manager.
* @param player - player to hook.
*/
public 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)) {
while (true) {
try {
injector = getHookInstance(player, currentHook);
injector.initialize();
injector.injectManager();
DataInputStream inputStream = injector.getInputStream(false);
if (!player.isOnline() || inputStream == null) {
throw new PlayerLoggedOutException();
}
playerInjection.put(player, injector);
connectionLookup.put(inputStream, player);
break;
} catch (PlayerLoggedOutException e) {
throw e;
} catch (Exception 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);
}
}
/**
* Unregisters the given player.
* @param player - player to unregister.
*/
public void uninjectPlayer(Player player) {
if (!hasClosed && player != null) {
PlayerInjector injector = playerInjection.get(player);
if (injector != null) {
DataInputStream input = injector.getInputStream(true);
injector.cleanupAll();
playerInjection.remove(player);
connectionLookup.remove(input);
}
}
}
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
getInjector(reciever).sendServerPacket(packet.getHandle(), filters);
}
private PlayerInjector getInjector(Player player) {
if (!playerInjection.containsKey(player)) {
// What? Try to inject again.
injectPlayer(player);
}
PlayerInjector injector = playerInjection.get(player);
// Check that the injector was sucessfully added
if (injector != null)
return injector;
else
throw new IllegalArgumentException("Player has no injected handler.");
}
/**
* Determine if the given listeners are valid.
* @param listeners - listeners to check.
*/
public void checkListener(Set<PacketListener> listeners) {
// Make sure the current listeners are compatible
if (lastSuccessfulHook != null) {
for (PacketListener listener : listeners) {
try {
checkListener(listener);
} catch (IllegalStateException e) {
logger.log(Level.WARNING, "Unsupported listener.", e);
}
}
}
}
/**
* 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);
}
}
/**
* Process a packet as if it were sent by the given player.
* @param player - the sender.
* @param mcPacket - the packet to process.
* @throws IllegalAccessException If the reflection machinery failed.
* @throws InvocationTargetException If the underlying method caused an error.
*/
public void processPacket(Player player, Packet mcPacket) throws IllegalAccessException, InvocationTargetException {
PlayerInjector injector = getInjector(player);
injector.processPacket(mcPacket);
}
/**
* Retrieve the current list of registered sending listeners.
* @return List of the sending listeners's packet IDs.
*/
public Set<Integer> getSendingFilters() {
return ImmutableSet.copyOf(sendingFilters);
}
/**
* Retrieve the current logger.
* @return Error logger.
*/
public Logger getLogger() {
return logger;
}
public void close() {
// Guard
if (hasClosed || playerInjection == null)
return;
// Remove everything
for (PlayerInjector injection : playerInjection.values()) {
if (injection != null) {
injection.cleanupAll();
}
}
// Remove server handler
serverInjection.cleanupAll();
hasClosed = true;
playerInjection.clear();
connectionLookup.clear();
invoker = null;
}
}

Datei anzeigen

@ -15,16 +15,18 @@
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.player;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Set; import java.util.logging.Level;
import java.util.logging.Logger;
import net.minecraft.server.EntityPlayer; import net.minecraft.server.EntityPlayer;
import net.minecraft.server.Packet; import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Factory;
import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -32,6 +34,7 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
@ -41,10 +44,15 @@ abstract class PlayerInjector {
// Cache previously retrieved fields // Cache previously retrieved fields
protected static Field serverHandlerField; protected static Field serverHandlerField;
protected static Field proxyServerField;
protected static Field networkManagerField; protected static Field networkManagerField;
protected static Field inputField; protected static Field inputField;
protected static Field netHandlerField; protected static Field netHandlerField;
// Whether or not we're using a proxy type
private static boolean hasProxyType;
// To add our injected array lists // To add our injected array lists
protected static StructureModifier<Object> networkModifier; protected static StructureModifier<Object> networkModifier;
@ -65,17 +73,18 @@ abstract class PlayerInjector {
protected Object netHandler; protected Object netHandler;
// The packet manager and filters // The packet manager and filters
protected PacketFilterManager manager; protected ListenerInvoker invoker;
protected Set<Integer> sendingFilters;
// Previous data input // Previous data input
protected DataInputStream cachedInput; protected DataInputStream cachedInput;
public PlayerInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException { // Handle errors
protected Logger logger;
public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException {
this.logger = logger;
this.player = player; this.player = player;
this.manager = manager; this.invoker = invoker;
this.sendingFilters = sendingFilters;
initialize();
} }
/** /**
@ -87,7 +96,11 @@ abstract class PlayerInjector {
return craft.getHandle(); return craft.getHandle();
} }
protected void initialize() throws IllegalAccessException { /**
* Initialize all fields for this player injector, if it hasn't already.
* @throws IllegalAccessException An error has occured.
*/
public void initialize() throws IllegalAccessException {
EntityPlayer notchEntity = getEntityPlayer(); EntityPlayer notchEntity = getEntityPlayer();
@ -96,8 +109,12 @@ abstract class PlayerInjector {
hasInitialized = true; hasInitialized = true;
// Retrieve the server handler // Retrieve the server handler
if (serverHandlerField == null) if (serverHandlerField == null) {
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler"); serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
proxyServerField = getProxyField(notchEntity, serverHandlerField);
}
// Yo dawg
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity); serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
serverHandler = serverHandlerRef.getValue(); serverHandler = serverHandlerRef.getValue();
@ -123,6 +140,49 @@ abstract class PlayerInjector {
} }
} }
/**
* Retrieve whether or not the server handler is a proxy object.
* @return TRUE if it is, FALSE otherwise.
*/
protected boolean hasProxyServerHandler() {
return hasProxyType;
}
private Field getProxyField(EntityPlayer notchEntity, Field serverField) {
try {
Object handler = FieldUtils.readField(serverHandlerField, notchEntity, true);
// Is this a Minecraft hook?
if (handler != null && !handler.getClass().getName().startsWith("net.minecraft.server")) {
// This is our proxy object
if (handler instanceof Factory)
return null;
hasProxyType = true;
logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!");
// No? Is it a Proxy type?
try {
FuzzyReflection reflection = FuzzyReflection.fromObject(handler, true);
// It might be
return reflection.getFieldByType(".*NetServerHandler");
} catch (RuntimeException e) {
// Damn
}
}
} catch (IllegalAccessException e) {
logger.warning("Unable to load server handler from proxy type.");
}
// Nope, just go with it
return null;
}
/** /**
* Retrieves the current net handler for this player. * Retrieves the current net handler for this player.
* @return Current net handler. * @return Current net handler.
@ -205,6 +265,12 @@ abstract class PlayerInjector {
*/ */
public abstract void cleanupAll(); public abstract void cleanupAll();
/**
* Determine if this inject method can even be attempted.
* @return TRUE if can be attempted, though possibly with failure, FALSE otherwise.
*/
public abstract boolean canInject();
/** /**
* Invoked before a new listener is registered. * Invoked before a new listener is registered.
* <p> * <p>
@ -218,16 +284,16 @@ abstract class PlayerInjector {
* @param packet - packet to recieve. * @param packet - packet to recieve.
* @return The given packet, or the packet replaced by the listeners. * @return The given packet, or the packet replaced by the listeners.
*/ */
Packet handlePacketRecieved(Packet packet) { public Packet handlePacketRecieved(Packet packet) {
// Get the packet ID too // Get the packet ID too
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass()); Integer id = invoker.getPacketID(packet);
// Make sure we're listening // Make sure we're listening
if (id != null && sendingFilters.contains(id)) { if (id != null && hasListener(id)) {
// A packet has been sent guys! // A packet has been sent guys!
PacketContainer container = new PacketContainer(id, packet); PacketContainer container = new PacketContainer(id, packet);
PacketEvent event = PacketEvent.fromServer(manager, container, player); PacketEvent event = PacketEvent.fromServer(invoker, container, player);
manager.invokePacketSending(event); invoker.invokePacketSending(event);
// Cancelling is pretty simple. Just ignore the packet. // Cancelling is pretty simple. Just ignore the packet.
if (event.isCancelled()) if (event.isCancelled())
@ -240,12 +306,24 @@ abstract class PlayerInjector {
return packet; return packet;
} }
/**
* Determine if the given injector is listening for this packet ID.
* @param packetID - packet ID to check.
* @return TRUE if it is, FALSE oterhwise.
*/
protected abstract boolean hasListener(int packetID);
/** /**
* Retrieve the current player's input stream. * Retrieve the current player's input stream.
* @param cache - whether or not to cache the result of this method. * @param cache - whether or not to cache the result of this method.
* @return The player's input stream. * @return The player's input stream.
*/ */
public DataInputStream getInputStream(boolean cache) { public DataInputStream getInputStream(boolean cache) {
if (inputField == null)
throw new IllegalStateException("Input field is NULL.");
if (networkManager == null)
throw new IllegalStateException("Network manager is NULL.");
// Get the associated input stream // Get the associated input stream
try { try {
if (cache && cachedInput != null) if (cache && cachedInput != null)

Datei anzeigen

@ -1,4 +1,4 @@
package com.comphenix.protocol.injector; package com.comphenix.protocol.injector.player;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -24,10 +24,21 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
this.underlyingList = underlyingList; this.underlyingList = underlyingList;
} }
/**
* Invoked when a element inserted is replaced.
* @param inserting - the element inserted.
* @param replacement - the element that it should replace.
*/
protected void onReplacing(TKey inserting, TKey replacement) {
// Default is to do nothing.
}
@Override @Override
public boolean add(TKey element) { public boolean add(TKey element) {
if (replaceMap.containsKey(element)) { if (replaceMap.containsKey(element)) {
return super.add(replaceMap.get(element)); TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
return super.add(replacement);
} else { } else {
return super.add(element); return super.add(element);
} }
@ -36,7 +47,9 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
@Override @Override
public void add(int index, TKey element) { public void add(int index, TKey element) {
if (replaceMap.containsKey(element)) { if (replaceMap.containsKey(element)) {
super.add(index, replaceMap.get(element)); TKey replacement = replaceMap.get(element);
onReplacing(element, replacement);
super.add(index, replacement);
} else { } else {
super.add(index, element); super.add(index, element);
} }
@ -101,8 +114,10 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
*/ */
public synchronized void replaceAll(TKey find, TKey replace) { public synchronized void replaceAll(TKey find, TKey replace) {
for (int i = 0; i < underlyingList.size(); i++) { for (int i = 0; i < underlyingList.size(); i++) {
if (Objects.equal(underlyingList.get(i), find)) if (Objects.equal(underlyingList.get(i), find)) {
onReplacing(find, replace);
underlyingList.set(i, replace); underlyingList.set(i, replace);
}
} }
} }
@ -121,7 +136,9 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
TKey replaced = underlyingList.get(i); TKey replaced = underlyingList.get(i);
if (inverse.containsKey(replaced)) { if (inverse.containsKey(replaced)) {
underlyingList.set(i, inverse.get(replaced)); TKey original = inverse.get(replaced);
onReplacing(replaced, original);
underlyingList.set(i, original);
} }
} }

Datei anzeigen

@ -292,7 +292,9 @@ public class FuzzyReflection {
// Like above, only here we test the field type // Like above, only here we test the field type
for (Field field : getFields()) { for (Field field : getFields()) {
if (match.matcher(field.getType().getName()).matches()) { String name = field.getType().getName();
if (match.matcher(name).matches()) {
return field; return field;
} }
} }

Datei anzeigen

@ -0,0 +1,70 @@
package com.comphenix.protocol.reflect;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Can copy an object field by field.
*
* @author Kristian
*/
public class ObjectCloner {
// Cache structure modifiers
@SuppressWarnings("rawtypes")
private static ConcurrentMap<Class, StructureModifier<Object>> cache =
new ConcurrentHashMap<Class, StructureModifier<Object>>();
/**
* Copy every field in object A to object B.
* <p>
* The two objects must have the same number of fields of the same type.
* @param source - fields to copy.
* @param destination - fields to copy to.
* @param commonType - type containing each field to copy.
*/
public static void copyTo(Object source, Object destination, Class<?> commonType) {
if (source == null)
throw new IllegalArgumentException("Source cannot be NULL");
if (destination == null)
throw new IllegalArgumentException("Destination cannot be NULL");
StructureModifier<Object> modifier = cache.get(commonType);
// Create the structure modifier if we haven't already
if (modifier == null) {
StructureModifier<Object> value = new StructureModifier<Object>(commonType, null, false);
modifier = cache.putIfAbsent(commonType, value);
if (modifier == null)
modifier = value;
}
// Add target
StructureModifier<Object> modifierSource = modifier.withTarget(source);
StructureModifier<Object> modifierDest = modifier.withTarget(destination);
// Copy every field
try {
for (int i = 0; i < modifierSource.size(); i++) {
Object value = modifierSource.read(i);
modifierDest.write(i, value);
// System.out.println(String.format("Writing value %s to %s",
// value, modifier.getFields().get(i).getName()));
}
// Copy private fields underneath
Class<?> superclass = commonType.getSuperclass();
if (!superclass.equals(Object.class)) {
copyTo(source, destination, superclass);
}
} catch (FieldAccessException e) {
throw new RuntimeException("Unable to copy fields from " + commonType.getName(), e);
}
}
}

Datei anzeigen

@ -41,13 +41,18 @@ public class DefaultInstances {
/** /**
* The maximum height of the hierachy of creates types. Used to prevent cycles. * The maximum height of the hierachy of creates types. Used to prevent cycles.
*/ */
private final static int MAXIMUM_RECURSION = 20; private int maximumRecursion = 20;
/** /**
* Ordered list of instance provider, from highest priority to lowest. * Ordered list of instance provider, from highest priority to lowest.
*/ */
private ImmutableList<InstanceProvider> registered; private ImmutableList<InstanceProvider> registered;
/**
* Whether or not the constructor must be non-null.
*/
private boolean nonNull;
/** /**
* Construct a default instance generator using the given instance providers. * Construct a default instance generator using the given instance providers.
* @param registered - list of instance providers. * @param registered - list of instance providers.
@ -56,6 +61,16 @@ public class DefaultInstances {
this.registered = registered; this.registered = registered;
} }
/**
* Copy a given instance provider.
* @param other - instance provider to copy.
*/
public DefaultInstances(DefaultInstances other) {
this.nonNull = other.nonNull;
this.maximumRecursion = other.maximumRecursion;
this.registered = other.registered;
}
/** /**
* Construct a default instance generator using the given instance providers. * Construct a default instance generator using the given instance providers.
* @param instaceProviders - array of instance providers. * @param instaceProviders - array of instance providers.
@ -81,6 +96,40 @@ public class DefaultInstances {
return registered; return registered;
} }
/**
* Retrieve whether or not the constructor's parameters must be non-null.
* @return TRUE if they must be non-null, FALSE otherwise.
*/
public boolean isNonNull() {
return nonNull;
}
/**
* Set whether or not the constructor's parameters must be non-null.
* @param nonNull - TRUE if they must be non-null, FALSE otherwise.
*/
public void setNonNull(boolean nonNull) {
this.nonNull = nonNull;
}
/**
* Retrieve the the maximum height of the hierachy of creates types.
* @return Maximum height.
*/
public int getMaximumRecursion() {
return maximumRecursion;
}
/**
* Set the maximum height of the hierachy of creates types. Used to prevent cycles.
* @param maximumRecursion - maximum recursion height.
*/
public void setMaximumRecursion(int maximumRecursion) {
if (maximumRecursion < 1)
throw new IllegalArgumentException("Maxmimum recursion height must be one or higher.");
this.maximumRecursion = maximumRecursion;
}
/** /**
* Retrieves a default instance or value that is assignable to this type. * Retrieves a default instance or value that is assignable to this type.
* <p> * <p>
@ -107,9 +156,12 @@ public class DefaultInstances {
* @param type - type to construct. * @param type - type to construct.
* @return A constructor with the fewest number of parameters, or NULL if the type has no constructors. * @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) { public <T> Constructor<T> getMinimumConstructor(Class<T> type) {
return getMinimumConstructor(type, registered, 0);
}
@SuppressWarnings("unchecked")
private <T> Constructor<T> getMinimumConstructor(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
Constructor<T> minimum = null; Constructor<T> minimum = null;
int lastCount = Integer.MAX_VALUE; int lastCount = Integer.MAX_VALUE;
@ -121,6 +173,13 @@ public class DefaultInstances {
// require itself in the constructor. // require itself in the constructor.
if (types.length < lastCount) { if (types.length < lastCount) {
if (!contains(types, type)) { if (!contains(types, type)) {
if (nonNull) {
// Make sure all of these types are non-null
if (isAnyNull(types, providers, recursionLevel)) {
continue;
}
}
minimum = (Constructor<T>) candidate; minimum = (Constructor<T>) candidate;
lastCount = types.length; lastCount = types.length;
@ -134,6 +193,27 @@ public class DefaultInstances {
return minimum; return minimum;
} }
/**
* Determine if any of the given types will be NULL once created.
* <p>
* Recursion level is the number of times the default method has been called.
* @param types - types to check.
* @param providers - instance providers.
* @param recursionLevel - current recursion level.
* @return
*/
private boolean isAnyNull(Class<?>[] types, List<InstanceProvider> providers, int recursionLevel) {
// Just check if any of them are NULL
for (Class<?> type : types) {
if (getDefaultInternal(type, providers, recursionLevel) == null) {
System.out.println(type.getName() + " is NULL!");
return true;
}
}
return false;
}
/** /**
* Retrieves a default instance or value that is assignable to this type. * Retrieves a default instance or value that is assignable to this type.
* <p> * <p>
@ -149,7 +229,7 @@ public class DefaultInstances {
* </ul> * </ul>
* </ul> * </ul>
* @param type - the type to construct a default value. * @param type - the type to construct a default value.
* @param providers - instance providers used during the * @param providers - instance providers used during the construction.
* @return A default value/instance, or NULL if not possible. * @return A default value/instance, or NULL if not possible.
*/ */
public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) { public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) {
@ -159,11 +239,7 @@ public class DefaultInstances {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) { private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
// Guard against recursion // The instance providiers should protect themselves against recursion
if (recursionLevel > MAXIMUM_RECURSION) {
return null;
}
for (InstanceProvider generator : providers) { for (InstanceProvider generator : providers) {
Object value = generator.create(type); Object value = generator.create(type);
@ -171,7 +247,12 @@ public class DefaultInstances {
return (T) value; return (T) value;
} }
Constructor<T> minimum = getMinimumConstructor(type); // Guard against recursion
if (recursionLevel >= maximumRecursion) {
return null;
}
Constructor<T> minimum = getMinimumConstructor(type, providers, recursionLevel + 1);
// Create the type with this constructor using default values. This might fail, though. // Create the type with this constructor using default values. This might fail, though.
try { try {
@ -183,6 +264,11 @@ public class DefaultInstances {
// Fill out // Fill out
for (int i = 0; i < parameterCount; i++) { for (int i = 0; i < parameterCount; i++) {
params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1); params[i] = getDefaultInternal(types[i], providers, recursionLevel + 1);
// Did we break the non-null contract?
if (params[i] == null && nonNull) {
return null;
}
} }
return createInstance(type, minimum, types, params); return createInstance(type, minimum, types, params);
@ -204,7 +290,7 @@ public class DefaultInstances {
public DefaultInstances forEnhancer(Enhancer enhancer) { public DefaultInstances forEnhancer(Enhancer enhancer) {
final Enhancer ex = enhancer; final Enhancer ex = enhancer;
return new DefaultInstances(registered) { return new DefaultInstances(this) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) { protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {

Datei anzeigen

@ -17,8 +17,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
*/ */
public class ExistingGenerator implements InstanceProvider { public class ExistingGenerator implements InstanceProvider {
@SuppressWarnings("rawtypes") private Map<String, Object> existingValues = new HashMap<String, Object>();
private Map<Class, Object> existingValues = new HashMap<Class, Object>();
private ExistingGenerator() { private ExistingGenerator() {
// Only accessible to the constructors // Only accessible to the constructors
@ -33,10 +32,34 @@ public class ExistingGenerator implements InstanceProvider {
* @return The instance generator. * @return The instance generator.
*/ */
public static ExistingGenerator fromObjectFields(Object object) { public static ExistingGenerator fromObjectFields(Object object) {
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
return fromObjectFields(object, object.getClass());
}
/**
* 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.
* @param type - the type to cast the object.
* @return The instance generator.
*/
public static ExistingGenerator fromObjectFields(Object object, Class<?> type) {
ExistingGenerator generator = new ExistingGenerator(); ExistingGenerator generator = new ExistingGenerator();
// Possible errors
if (object == null)
throw new IllegalArgumentException("Object cannot be NULL.");
if (type == null)
throw new IllegalArgumentException("Type cannot be NULL.");
if (!type.isAssignableFrom(object.getClass()))
throw new IllegalArgumentException("Type must be a superclass or be the same type.");
// Read instances from every field. // Read instances from every field.
for (Field field : FuzzyReflection.fromObject(object, true).getFields()) { for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
try { try {
Object value = FieldUtils.readField(field, object, true); Object value = FieldUtils.readField(field, object, true);
@ -70,18 +93,17 @@ public class ExistingGenerator implements InstanceProvider {
if (value == null) if (value == null)
throw new IllegalArgumentException("Value cannot be NULL."); throw new IllegalArgumentException("Value cannot be NULL.");
existingValues.put(value.getClass(), value); existingValues.put(value.getClass().getName(), value);
} }
private void addObject(Class<?> type, Object value) { private void addObject(Class<?> type, Object value) {
existingValues.put(type, value); existingValues.put(type.getName(), value);
} }
@Override @Override
public Object create(@Nullable Class<?> type) { public Object create(@Nullable Class<?> type) {
Object value = existingValues.get(type); Object value = existingValues.get(type.getName());
// NULL values indicate that the generator failed // NULL values indicate that the generator failed
return value; return value;

Datei anzeigen

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