Merge branch 'master' into gh-pages
Dieser Commit ist enthalten in:
Commit
f56cf3290e
@ -20,7 +20,6 @@ public interface AsynchronousManager {
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param plugin - the plugin that is registering the handler.
|
||||
* @param listener - the packet listener that will recieve these asynchronous events.
|
||||
* @return An asynchrouns handler.
|
||||
*/
|
||||
|
@ -53,6 +53,7 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
// Server packets are synchronized already
|
||||
this.serverQueue = new PacketSendingQueue(false);
|
||||
|
||||
// Client packets must be synchronized
|
||||
this.clientQueue = new PacketSendingQueue(true);
|
||||
|
||||
@ -68,28 +69,40 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
boolean hasListener = true;
|
||||
|
||||
// Add listener to either or both processing queue
|
||||
if (hasValidWhitelist(sendingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, sendingWhitelist);
|
||||
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||
hasListener &= hasPacketListener(sendingWhitelist);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(receivingWhitelist)) {
|
||||
PacketFilterManager.verifyWhitelist(listener, receivingWhitelist);
|
||||
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||
hasListener &= hasPacketListener(receivingWhitelist);
|
||||
}
|
||||
|
||||
if (!hasListener) {
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
if (autoInject) {
|
||||
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||
manager.addPacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
@ -97,15 +110,6 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packets are represented.
|
||||
* @param whitelist - list of packets.
|
||||
* @return TRUE if they are all registered, FALSE otherwise.
|
||||
*/
|
||||
private boolean hasPacketListener(ListeningWhitelist whitelist) {
|
||||
return manager.getSendingFilters().containsAll(whitelist.getWhitelist());
|
||||
}
|
||||
|
||||
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||
return whitelist != null && whitelist.getWhitelist().size() > 0;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@ -17,18 +21,26 @@ import com.comphenix.protocol.events.PacketListener;
|
||||
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());
|
||||
|
||||
/**
|
||||
* 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
|
||||
private static int DEFAULT_CAPACITY = 1024;
|
||||
|
||||
// Cancel the async handler
|
||||
private volatile boolean cancelled;
|
||||
|
||||
// If we've started the listener loop before
|
||||
private volatile boolean started;
|
||||
// Number of worker threads
|
||||
private final AtomicInteger started = new AtomicInteger();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
@ -40,6 +52,10 @@ public class AsyncListenerHandler {
|
||||
// List of queued packets
|
||||
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
|
||||
private Thread mainThread;
|
||||
|
||||
@ -78,16 +94,24 @@ public class AsyncListenerHandler {
|
||||
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.
|
||||
*/
|
||||
public void cancel() {
|
||||
// Remove the listener as quickly as possible
|
||||
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>
|
||||
* <b>Warning</b>: Never call the run() method in the main thread.
|
||||
*/
|
||||
public Runnable getListenerLoop() {
|
||||
return new Runnable() {
|
||||
public AsyncRunnable getListenerLoop() {
|
||||
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
|
||||
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
|
||||
private void listenerLoop() {
|
||||
private void listenerLoop(int workerID) {
|
||||
|
||||
// Danger, danger!
|
||||
if (Thread.currentThread().getId() == mainThread.getId())
|
||||
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||
if (started)
|
||||
throw new IllegalStateException("A listener cannot be run by multiple threads. Create a new listener instead.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||
|
||||
// Proceed
|
||||
started = true;
|
||||
|
||||
try {
|
||||
// Wait if certain threads are stopping
|
||||
if (waitForStops())
|
||||
return;
|
||||
|
||||
// Proceed
|
||||
started.incrementAndGet();
|
||||
|
||||
mainLoop:
|
||||
while (!cancelled) {
|
||||
PacketEvent packet = queuedPackets.take();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
|
||||
// Handle cancel requests
|
||||
if (packet == null || marker == null || !packet.isAsynchronous()) {
|
||||
break;
|
||||
if (packet == null || marker == null || packet == INTERUPT_PACKET) {
|
||||
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
|
||||
try {
|
||||
marker.setListenerHandler(this);
|
||||
marker.setWorkerID(workerID);
|
||||
|
||||
if (packet.isServerPacket())
|
||||
listener.onPacketSending(packet);
|
||||
else
|
||||
@ -171,40 +357,35 @@ public class AsyncListenerHandler {
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// We're done
|
||||
}
|
||||
|
||||
} finally {
|
||||
// Clean up
|
||||
started.decrementAndGet();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
private synchronized void close() {
|
||||
// Remove the listener itself
|
||||
if (!cancelled) {
|
||||
filterManager.unregisterAsyncHandlerInternal(this);
|
||||
cancelled = true;
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getPluginName() {
|
||||
return PacketAdapter.getPluginName(listener);
|
||||
// Tell every uncancelled thread to end
|
||||
stopThreads();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the plugin associated with this async listener.
|
||||
* @return The plugin.
|
||||
* Use the poision pill method to stop every worker thread.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return listener != null ? listener.getPlugin() : null;
|
||||
private void stopThreads() {
|
||||
// Poison Pill Shutdown
|
||||
queuedPackets.clear();
|
||||
stop(started.get());
|
||||
|
||||
// Individual shut down is irrelevant now
|
||||
synchronized (stopLock) {
|
||||
stopLock.notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the asynchronous listener using the Bukkit scheduler.
|
||||
*/
|
||||
public void start() {
|
||||
if (listener.getPlugin() == null)
|
||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||
|
||||
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
|
||||
// Used to identify the asynchronous worker
|
||||
private AsyncListenerHandler listenerHandler;
|
||||
private int workerID;
|
||||
|
||||
// Determine if Minecraft processes this packet asynchronously
|
||||
private static Method isMinecraftAsync;
|
||||
private static boolean alwaysSync;
|
||||
@ -215,11 +219,47 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
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.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
public Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
|
||||
|
35
ProtocolLib/src/com/comphenix/protocol/async/AsyncRunnable.java
Normale Datei
35
ProtocolLib/src/com/comphenix/protocol/async/AsyncRunnable.java
Normale Datei
@ -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();
|
||||
}
|
40
ProtocolLib/src/com/comphenix/protocol/async/PacketEventHolder.java
Normale Datei
40
ProtocolLib/src/com/comphenix/protocol/async/PacketEventHolder.java
Normale Datei
@ -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();
|
||||
}
|
||||
}
|
@ -2,12 +2,14 @@ package com.comphenix.protocol.async;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.google.common.collect.MinMaxPriorityQueue;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the processing of every packet type.
|
||||
@ -16,6 +18,9 @@ import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
*/
|
||||
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||
|
||||
// Initial number of elements
|
||||
public static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to process concurrently.
|
||||
*/
|
||||
@ -33,18 +38,23 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
||||
private Semaphore concurrentProcessing;
|
||||
|
||||
// Queued packets for being processed
|
||||
private ArrayBlockingQueue<PacketEvent> processingQueue;
|
||||
private Queue<PacketEventHolder> processingQueue;
|
||||
|
||||
// Packets for sending
|
||||
private 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();
|
||||
this.processingQueue = new ArrayBlockingQueue<PacketEvent>(queueLimit);
|
||||
|
||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||
expectedSize(initialSize).
|
||||
maximumSize(maximumSize).
|
||||
<PacketEventHolder>create(), null);
|
||||
|
||||
this.maximumConcurrency = maximumConcurrency;
|
||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||
this.sendingQueue = sendingQueue;
|
||||
@ -58,7 +68,7 @@ class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncList
|
||||
*/
|
||||
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||
try {
|
||||
processingQueue.add(packet);
|
||||
processingQueue.add(new PacketEventHolder(packet));
|
||||
|
||||
// Begin processing packets
|
||||
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.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void signalBeginProcessing(boolean onMainThread) {
|
||||
while (concurrentProcessing.tryAcquire()) {
|
||||
PacketEvent packet = processingQueue.poll();
|
||||
PacketEventHolder holder = processingQueue.poll();
|
||||
|
||||
// Any packet queued?
|
||||
if (packet != null) {
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
|
||||
if (holder != null) {
|
||||
PacketEvent packet = holder.getEvent();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketID());
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
|
@ -1,15 +1,15 @@
|
||||
package com.comphenix.protocol.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
|
||||
/**
|
||||
* Represents packets ready to be transmitted to a client.
|
||||
@ -17,28 +17,28 @@ import com.google.common.collect.ComparisonChain;
|
||||
*/
|
||||
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
|
||||
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.
|
||||
* @param synchronizeMain - whether or not to synchronize with the main thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean synchronizeMain) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<PacketEventHolder>(INITIAL_CAPACITY);
|
||||
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
|
||||
*/
|
||||
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);
|
||||
|
||||
// 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())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
@ -88,9 +90,10 @@ class PacketSendingQueue {
|
||||
|
||||
// Transmit as many packets as we can
|
||||
while (true) {
|
||||
PacketEvent current = sendingQueue.peek();
|
||||
PacketEventHolder holder = sendingQueue.peek();
|
||||
|
||||
if (current != null) {
|
||||
if (holder != null) {
|
||||
PacketEvent current = holder.getEvent();
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
|
||||
// Abort if we're not on the main thread
|
||||
@ -111,8 +114,11 @@ class PacketSendingQueue {
|
||||
|
||||
if (marker.isProcessed() || marker.hasExpired()) {
|
||||
if (marker.isProcessed() && !current.isCancelled()) {
|
||||
// Silently skip players that have logged out
|
||||
if (isOnline(current.getPlayer())) {
|
||||
sendPacket(current);
|
||||
}
|
||||
}
|
||||
|
||||
sendingQueue.poll();
|
||||
continue;
|
||||
@ -124,15 +130,19 @@ class PacketSendingQueue {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOnline(Player player) {
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEvent current = sendingQueue.poll();
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (current != null) {
|
||||
sendPacket(current);
|
||||
if (holder != null) {
|
||||
sendPacket(holder.getEvent());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
211
ProtocolLib/src/com/comphenix/protocol/async/Synchronization.java
Normale Datei
211
ProtocolLib/src/com/comphenix/protocol/async/Synchronization.java
Normale Datei
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
ProtocolLib/src/com/comphenix/protocol/injector/ListenerInvoker.java
Normale Datei
27
ProtocolLib/src/com/comphenix/protocol/injector/ListenerInvoker.java
Normale Datei
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -17,13 +17,10 @@
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
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.AsyncMarker;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
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.
|
||||
@ -87,21 +85,11 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
private Set<PacketListener> packetListeners =
|
||||
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
|
||||
private PacketInjector packetInjector;
|
||||
|
||||
// Server connection injection
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// Enabled packet filters
|
||||
private Set<Integer> sendingFilters = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
|
||||
// Player injection
|
||||
private PlayerInjectionHandler playerInjection;
|
||||
|
||||
// The two listener containers
|
||||
private SortedPacketListenerList recievedListeners = new SortedPacketListenerList();
|
||||
@ -113,9 +101,6 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
// The default class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// The last successful player hook
|
||||
private PlayerInjector lastSuccessfulHook;
|
||||
|
||||
// Error logger
|
||||
private Logger logger;
|
||||
|
||||
@ -135,9 +120,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
// Initialize values
|
||||
this.classLoader = classLoader;
|
||||
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.serverInjection = new InjectedServerConnection(logger, server);
|
||||
} catch (IllegalAccessException 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
this.playerHook = playerHook;
|
||||
playerInjection.setPlayerHook(playerHook);
|
||||
|
||||
// Make sure the current listeners are compatible
|
||||
if (lastSuccessfulHook != null) {
|
||||
for (PacketListener listener : packetListeners) {
|
||||
checkListener(listener);
|
||||
}
|
||||
}
|
||||
playerInjection.checkListener(packetListeners);
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
@ -200,14 +181,14 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
verifyWhitelist(listener, sending);
|
||||
sendingListeners.addListener(listener, sending);
|
||||
enablePacketFilters(ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
||||
|
||||
// Make sure this is possible
|
||||
playerInjection.checkListener(listener);
|
||||
}
|
||||
if (hasReceiving) {
|
||||
verifyWhitelist(listener, receiving);
|
||||
recievedListeners.addListener(listener, receiving);
|
||||
enablePacketFilters(ConnectionSide.CLIENT_SIDE, receiving.getWhitelist());
|
||||
|
||||
// We don't know if we've hooked any players yet
|
||||
checkListener(listener);
|
||||
}
|
||||
|
||||
// Inform our injected hooks
|
||||
@ -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
|
||||
public void removePacketListener(PacketListener listener) {
|
||||
if (listener == null)
|
||||
@ -288,18 +255,12 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
asyncFilterManager.unregisterAsyncHandlers(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
@Override
|
||||
public void invokePacketRecieving(PacketEvent event) {
|
||||
handlePacket(recievedListeners, event, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
@Override
|
||||
public void invokePacketSending(PacketEvent event) {
|
||||
handlePacket(sendingListeners, event, true);
|
||||
}
|
||||
@ -352,7 +313,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
sendingFilters.add(packetID);
|
||||
playerInjection.addPacketHandler(packetID);
|
||||
if (side.isForClient() && packetInjector != null)
|
||||
packetInjector.addPacketHandler(packetID);
|
||||
}
|
||||
@ -369,7 +330,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
for (int packetID : packets) {
|
||||
if (side.isForServer())
|
||||
sendingFilters.remove(packetID);
|
||||
playerInjection.removePacketHandler(packetID);
|
||||
if (side.isForClient() && packetInjector != null)
|
||||
packetInjector.removePacketHandler(packetID);
|
||||
}
|
||||
@ -387,7 +348,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet cannot be NULL.");
|
||||
|
||||
getInjector(reciever).sendServerPacket(packet.getHandle(), filters);
|
||||
playerInjection.sendServerPacket(reciever, packet, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -403,17 +364,21 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
if (packet == null)
|
||||
throw new IllegalArgumentException("packet cannot be NULL.");
|
||||
|
||||
PlayerInjector injector = getInjector(sender);
|
||||
Packet mcPacket = packet.getHandle();
|
||||
|
||||
// Make sure the packet isn't cancelled
|
||||
packetInjector.undoCancel(packet.getID(), mcPacket);
|
||||
|
||||
if (filters) {
|
||||
mcPacket = injector.handlePacketRecieved(mcPacket);
|
||||
PacketEvent event = packetInjector.packetRecieved(packet, sender);
|
||||
|
||||
if (!event.isCancelled())
|
||||
mcPacket = event.getPacket().getHandle();
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
injector.processPacket(mcPacket);
|
||||
playerInjection.processPacket(sender, mcPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -444,7 +409,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return ImmutableSet.copyOf(sendingFilters);
|
||||
return playerInjection.getSendingFilters();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -463,81 +428,7 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
*/
|
||||
public void initializePlayers(Player[] players) {
|
||||
for (Player player : players)
|
||||
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);
|
||||
}
|
||||
playerInjection.injectPlayer(player);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -550,17 +441,17 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
try {
|
||||
manager.registerEvents(new Listener() {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
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) {
|
||||
uninjectPlayer(event.getPlayer());
|
||||
playerInjection.uninjectPlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPluginDisabled(PluginDisableEvent event) {
|
||||
// Clean up in case the plugin forgets
|
||||
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.
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
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");
|
||||
|
||||
// Get the priority
|
||||
Object priorityNormal = Enum.valueOf(eventPriority, "Normal");
|
||||
Object priorityNormal = Enum.valueOf(eventPriority, "Highest");
|
||||
|
||||
// Get event types
|
||||
Object playerJoinType = Enum.valueOf(eventTypes, "PLAYER_JOIN");
|
||||
@ -617,9 +516,9 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
// Check for the correct event
|
||||
if (event instanceof PlayerJoinEvent)
|
||||
injectPlayer(((PlayerJoinEvent) event).getPlayer());
|
||||
playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer());
|
||||
else if (event instanceof PlayerQuitEvent)
|
||||
uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
|
||||
playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer());
|
||||
}
|
||||
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.
|
||||
* @return Class loader.
|
||||
@ -707,28 +576,19 @@ public final class PacketFilterManager implements ProtocolManager {
|
||||
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed || playerInjection == null)
|
||||
if (hasClosed)
|
||||
return;
|
||||
|
||||
// Remove everything
|
||||
for (PlayerInjector injection : playerInjection.values()) {
|
||||
if (injection != null) {
|
||||
injection.cleanupAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove packet handlers
|
||||
if (packetInjector != null)
|
||||
packetInjector.cleanupAll();
|
||||
|
||||
// Remove server handler
|
||||
serverInjection.cleanupAll();
|
||||
playerInjection.close();
|
||||
hasClosed = true;
|
||||
|
||||
// Remove listeners
|
||||
packetListeners.clear();
|
||||
playerInjection.clear();
|
||||
connectionLookup.clear();
|
||||
|
||||
// Clean up async handlers. We have to do this last.
|
||||
asyncFilterManager.cleanupAll();
|
||||
|
@ -33,6 +33,7 @@ import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
|
||||
@ -48,10 +49,10 @@ class PacketInjector {
|
||||
private static Object intHashMap;
|
||||
|
||||
// The packet filter manager
|
||||
private PacketFilterManager manager;
|
||||
private ListenerInvoker manager;
|
||||
|
||||
// Allows us to determine the sender
|
||||
private Map<DataInputStream, Player> playerLookup;
|
||||
private PlayerInjectionHandler playerInjection;
|
||||
|
||||
// Allows us to look up read packet injectors
|
||||
private Map<Integer, ReadPacketModifier> readModifier;
|
||||
@ -59,12 +60,12 @@ class PacketInjector {
|
||||
// Class loader
|
||||
private ClassLoader classLoader;
|
||||
|
||||
public PacketInjector(ClassLoader classLoader, PacketFilterManager manager,
|
||||
Map<DataInputStream, Player> playerLookup) throws IllegalAccessException {
|
||||
public PacketInjector(ClassLoader classLoader, ListenerInvoker manager,
|
||||
PlayerInjectionHandler playerInjection) throws IllegalAccessException {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.manager = manager;
|
||||
this.playerLookup = playerLookup;
|
||||
this.playerInjection = playerInjection;
|
||||
this.readModifier = new ConcurrentHashMap<Integer, ReadPacketModifier>();
|
||||
initialize();
|
||||
}
|
||||
@ -194,7 +195,18 @@ class PacketInjector {
|
||||
// Called from the ReadPacketModified monitor
|
||||
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);
|
||||
|
||||
manager.invokePacketRecieving(event);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
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.
|
||||
*
|
@ -1,4 +1,4 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
@ -7,10 +7,13 @@ import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.ObjectCloner;
|
||||
import com.comphenix.protocol.reflect.VolatileField;
|
||||
|
||||
/**
|
||||
@ -134,18 +137,33 @@ class InjectedServerConnection {
|
||||
if (list instanceof ReplacedArrayList) {
|
||||
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||
} else {
|
||||
replacedLists.add(new ReplacedArrayList<Object>(list));
|
||||
replacedLists.add(createReplacement(list));
|
||||
listFieldRef.setValue(replacedLists.get(0));
|
||||
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.
|
||||
* @param oldHandler - old server handler.
|
||||
* @param newHandler - new, proxied server handler.
|
||||
*/
|
||||
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
||||
|
||||
if (!hasAttempted) {
|
||||
injectList();
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@ -7,12 +7,14 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
@ -46,12 +48,27 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
private static Field syncField;
|
||||
private Object syncObject;
|
||||
|
||||
public NetworkFieldInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
|
||||
super(player, manager, sendingFilters);
|
||||
// Determine if we're listening
|
||||
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
|
||||
protected synchronized void initialize() throws IllegalAccessException {
|
||||
protected boolean hasListener(int packetID) {
|
||||
return sendingFilters.contains(packetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void initialize() throws IllegalAccessException {
|
||||
super.initialize();
|
||||
|
||||
// Get the sync field as well
|
||||
@ -112,7 +129,7 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
|
||||
synchronized(syncObject) {
|
||||
// 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
|
||||
for (Packet packet : minecraftList) {
|
||||
@ -154,4 +171,9 @@ class NetworkFieldInjector extends PlayerInjector {
|
||||
}
|
||||
overridenLists.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInject() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
@ -8,12 +8,14 @@ import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
|
||||
/**
|
||||
* Injection method that overrides the NetworkHandler itself, and it's sendPacket-method.
|
||||
@ -21,8 +23,17 @@ import com.comphenix.protocol.events.PacketListener;
|
||||
* @author Kristian
|
||||
*/
|
||||
class NetworkObjectInjector extends PlayerInjector {
|
||||
public NetworkObjectInjector(Player player, PacketFilterManager manager, Set<Integer> sendingFilters) throws IllegalAccessException {
|
||||
super(player, manager, sendingFilters);
|
||||
// Determine if we're listening
|
||||
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
|
||||
@ -107,4 +118,9 @@ class NetworkObjectInjector extends PlayerInjector {
|
||||
// Clean up
|
||||
networkManagerRef.revertValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInject() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -15,16 +15,18 @@
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
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.Packet;
|
||||
import net.sf.cglib.proxy.Factory;
|
||||
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
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.PacketEvent;
|
||||
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.StructureModifier;
|
||||
@ -41,10 +44,15 @@ abstract class PlayerInjector {
|
||||
|
||||
// Cache previously retrieved fields
|
||||
protected static Field serverHandlerField;
|
||||
protected static Field proxyServerField;
|
||||
|
||||
protected static Field networkManagerField;
|
||||
protected static Field inputField;
|
||||
protected static Field netHandlerField;
|
||||
|
||||
// Whether or not we're using a proxy type
|
||||
private static boolean hasProxyType;
|
||||
|
||||
// To add our injected array lists
|
||||
protected static StructureModifier<Object> networkModifier;
|
||||
|
||||
@ -65,17 +73,18 @@ abstract class PlayerInjector {
|
||||
protected Object netHandler;
|
||||
|
||||
// The packet manager and filters
|
||||
protected PacketFilterManager manager;
|
||||
protected Set<Integer> sendingFilters;
|
||||
protected ListenerInvoker invoker;
|
||||
|
||||
// Previous data input
|
||||
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.manager = manager;
|
||||
this.sendingFilters = sendingFilters;
|
||||
initialize();
|
||||
this.invoker = invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +96,11 @@ abstract class PlayerInjector {
|
||||
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();
|
||||
|
||||
@ -96,8 +109,12 @@ abstract class PlayerInjector {
|
||||
hasInitialized = true;
|
||||
|
||||
// Retrieve the server handler
|
||||
if (serverHandlerField == null)
|
||||
if (serverHandlerField == null) {
|
||||
serverHandlerField = FuzzyReflection.fromObject(notchEntity).getFieldByType(".*NetServerHandler");
|
||||
proxyServerField = getProxyField(notchEntity, serverHandlerField);
|
||||
}
|
||||
|
||||
// Yo dawg
|
||||
serverHandlerRef = new VolatileField(serverHandlerField, notchEntity);
|
||||
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.
|
||||
* @return Current net handler.
|
||||
@ -205,6 +265,12 @@ abstract class PlayerInjector {
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
@ -218,16 +284,16 @@ abstract class PlayerInjector {
|
||||
* @param packet - packet to recieve.
|
||||
* @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
|
||||
Integer id = MinecraftRegistry.getPacketToID().get(packet.getClass());
|
||||
Integer id = invoker.getPacketID(packet);
|
||||
|
||||
// Make sure we're listening
|
||||
if (id != null && sendingFilters.contains(id)) {
|
||||
if (id != null && hasListener(id)) {
|
||||
// A packet has been sent guys!
|
||||
PacketContainer container = new PacketContainer(id, packet);
|
||||
PacketEvent event = PacketEvent.fromServer(manager, container, player);
|
||||
manager.invokePacketSending(event);
|
||||
PacketEvent event = PacketEvent.fromServer(invoker, container, player);
|
||||
invoker.invokePacketSending(event);
|
||||
|
||||
// Cancelling is pretty simple. Just ignore the packet.
|
||||
if (event.isCancelled())
|
||||
@ -240,12 +306,24 @@ abstract class PlayerInjector {
|
||||
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.
|
||||
* @param cache - whether or not to cache the result of this method.
|
||||
* @return The player's input stream.
|
||||
*/
|
||||
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
|
||||
try {
|
||||
if (cache && cachedInput != null)
|
@ -1,4 +1,4 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -24,10 +24,21 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
|
||||
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
|
||||
public boolean add(TKey element) {
|
||||
if (replaceMap.containsKey(element)) {
|
||||
return super.add(replaceMap.get(element));
|
||||
TKey replacement = replaceMap.get(element);
|
||||
onReplacing(element, replacement);
|
||||
return super.add(replacement);
|
||||
} else {
|
||||
return super.add(element);
|
||||
}
|
||||
@ -36,7 +47,9 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
|
||||
@Override
|
||||
public void add(int index, TKey element) {
|
||||
if (replaceMap.containsKey(element)) {
|
||||
super.add(index, replaceMap.get(element));
|
||||
TKey replacement = replaceMap.get(element);
|
||||
onReplacing(element, replacement);
|
||||
super.add(index, replacement);
|
||||
} else {
|
||||
super.add(index, element);
|
||||
}
|
||||
@ -101,10 +114,12 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
|
||||
*/
|
||||
public synchronized void replaceAll(TKey find, TKey replace) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo all replacements.
|
||||
@ -121,7 +136,9 @@ class ReplacedArrayList<TKey> extends ForwardingList<TKey> {
|
||||
TKey replaced = underlyingList.get(i);
|
||||
|
||||
if (inverse.containsKey(replaced)) {
|
||||
underlyingList.set(i, inverse.get(replaced));
|
||||
TKey original = inverse.get(replaced);
|
||||
onReplacing(replaced, original);
|
||||
underlyingList.set(i, original);
|
||||
}
|
||||
}
|
||||
|
@ -292,7 +292,9 @@ public class FuzzyReflection {
|
||||
|
||||
// Like above, only here we test the field type
|
||||
for (Field field : getFields()) {
|
||||
if (match.matcher(field.getType().getName()).matches()) {
|
||||
String name = field.getType().getName();
|
||||
|
||||
if (match.matcher(name).matches()) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
70
ProtocolLib/src/com/comphenix/protocol/reflect/ObjectCloner.java
Normale Datei
70
ProtocolLib/src/com/comphenix/protocol/reflect/ObjectCloner.java
Normale Datei
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -41,13 +41,18 @@ public class DefaultInstances {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
* @param registered - list of instance providers.
|
||||
@ -56,6 +61,16 @@ public class DefaultInstances {
|
||||
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.
|
||||
* @param instaceProviders - array of instance providers.
|
||||
@ -81,6 +96,40 @@ public class DefaultInstances {
|
||||
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.
|
||||
* <p>
|
||||
@ -107,9 +156,12 @@ public class DefaultInstances {
|
||||
* @param type - type to construct.
|
||||
* @return A constructor with the fewest number of parameters, or NULL if the type has no constructors.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Constructor<T> getMinimumConstructor(Class<T> type) {
|
||||
return getMinimumConstructor(type, registered, 0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Constructor<T> getMinimumConstructor(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
|
||||
Constructor<T> minimum = null;
|
||||
int lastCount = Integer.MAX_VALUE;
|
||||
|
||||
@ -121,6 +173,13 @@ public class DefaultInstances {
|
||||
// require itself in the constructor.
|
||||
if (types.length < lastCount) {
|
||||
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;
|
||||
lastCount = types.length;
|
||||
|
||||
@ -134,6 +193,27 @@ public class DefaultInstances {
|
||||
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.
|
||||
* <p>
|
||||
@ -149,7 +229,7 @@ public class DefaultInstances {
|
||||
* </ul>
|
||||
* </ul>
|
||||
* @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.
|
||||
*/
|
||||
public <T> T getDefault(Class<T> type, List<InstanceProvider> providers) {
|
||||
@ -159,11 +239,7 @@ public class DefaultInstances {
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getDefaultInternal(Class<T> type, List<InstanceProvider> providers, int recursionLevel) {
|
||||
|
||||
// Guard against recursion
|
||||
if (recursionLevel > MAXIMUM_RECURSION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The instance providiers should protect themselves against recursion
|
||||
for (InstanceProvider generator : providers) {
|
||||
Object value = generator.create(type);
|
||||
|
||||
@ -171,7 +247,12 @@ public class DefaultInstances {
|
||||
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.
|
||||
try {
|
||||
@ -183,6 +264,11 @@ public class DefaultInstances {
|
||||
// Fill out
|
||||
for (int i = 0; i < parameterCount; i++) {
|
||||
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);
|
||||
@ -204,7 +290,7 @@ public class DefaultInstances {
|
||||
public DefaultInstances forEnhancer(Enhancer enhancer) {
|
||||
final Enhancer ex = enhancer;
|
||||
|
||||
return new DefaultInstances(registered) {
|
||||
return new DefaultInstances(this) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
|
||||
|
@ -17,8 +17,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
*/
|
||||
public class ExistingGenerator implements InstanceProvider {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Map<Class, Object> existingValues = new HashMap<Class, Object>();
|
||||
private Map<String, Object> existingValues = new HashMap<String, Object>();
|
||||
|
||||
private ExistingGenerator() {
|
||||
// Only accessible to the constructors
|
||||
@ -33,10 +32,34 @@ public class ExistingGenerator implements InstanceProvider {
|
||||
* @return The instance generator.
|
||||
*/
|
||||
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();
|
||||
|
||||
// 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.
|
||||
for (Field field : FuzzyReflection.fromObject(object, true).getFields()) {
|
||||
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
|
||||
try {
|
||||
Object value = FieldUtils.readField(field, object, true);
|
||||
|
||||
@ -70,18 +93,17 @@ public class ExistingGenerator implements InstanceProvider {
|
||||
if (value == 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) {
|
||||
existingValues.put(type, value);
|
||||
existingValues.put(type.getName(), value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object create(@Nullable Class<?> type) {
|
||||
|
||||
Object value = existingValues.get(type);
|
||||
Object value = existingValues.get(type.getName());
|
||||
|
||||
// NULL values indicate that the generator failed
|
||||
return value;
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: ProtocolLib
|
||||
version: 1.2.0
|
||||
version: 1.3.0
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren