mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-30 22:53:26 +01:00
Added support for Spigot with Netty disabled.
I've also added BukkitExecutors to ProtocolLib, which any plugin that depend on ProtocolLib may now use.
This commit is contained in:
parent
81e158d74a
commit
5e35f46b96
@ -200,6 +200,12 @@
|
|||||||
<version>2.2.2</version>
|
<version>2.2.2</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.comphenix.executors</groupId>
|
||||||
|
<artifactId>BukkitExecutors</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bukkit</groupId>
|
<groupId>org.bukkit</groupId>
|
||||||
<artifactId>craftbukkit</artifactId>
|
<artifactId>craftbukkit</artifactId>
|
||||||
|
@ -41,6 +41,7 @@ import com.comphenix.protocol.error.ErrorReporter;
|
|||||||
import com.comphenix.protocol.error.Report;
|
import com.comphenix.protocol.error.Report;
|
||||||
import com.comphenix.protocol.error.ReportType;
|
import com.comphenix.protocol.error.ReportType;
|
||||||
import com.comphenix.protocol.injector.DelayedSingleTask;
|
import com.comphenix.protocol.injector.DelayedSingleTask;
|
||||||
|
import com.comphenix.protocol.injector.InternalManager;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
import com.comphenix.protocol.metrics.Statistics;
|
import com.comphenix.protocol.metrics.Statistics;
|
||||||
@ -95,7 +96,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
private static final String PERMISSION_INFO = "protocol.info";
|
private static final String PERMISSION_INFO = "protocol.info";
|
||||||
|
|
||||||
// There should only be one protocol manager, so we'll make it static
|
// There should only be one protocol manager, so we'll make it static
|
||||||
private static PacketFilterManager protocolManager;
|
private static InternalManager protocolManager;
|
||||||
|
|
||||||
// Error reporter
|
// Error reporter
|
||||||
private static ErrorReporter reporter = new BasicErrorReporter();
|
private static ErrorReporter reporter = new BasicErrorReporter();
|
||||||
@ -172,7 +173,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
|
||||||
|
|
||||||
unhookTask = new DelayedSingleTask(this);
|
unhookTask = new DelayedSingleTask(this);
|
||||||
protocolManager = new PacketFilterManager(
|
protocolManager = PacketFilterManager.createManager(
|
||||||
getClassLoader(), getServer(), this, version, unhookTask, reporter);
|
getClassLoader(), getServer(), this, version, unhookTask, reporter);
|
||||||
|
|
||||||
// Setup error reporter
|
// Setup error reporter
|
||||||
@ -181,7 +182,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
// Update injection hook
|
// Update injection hook
|
||||||
try {
|
try {
|
||||||
PlayerInjectHooks hook = config.getInjectionMethod();
|
PlayerInjectHooks hook = config.getInjectionMethod();
|
||||||
|
|
||||||
// Only update the hook if it's different
|
// Only update the hook if it's different
|
||||||
if (!protocolManager.getPlayerHook().equals(hook)) {
|
if (!protocolManager.getPlayerHook().equals(hook)) {
|
||||||
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
|
logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook);
|
||||||
@ -302,9 +303,6 @@ public class ProtocolLibrary extends JavaPlugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform logic when the world has loaded
|
|
||||||
protocolManager.postWorldLoaded();
|
|
||||||
|
|
||||||
// Initialize background compiler
|
// Initialize background compiler
|
||||||
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
|
if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) {
|
||||||
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
|
backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter);
|
||||||
|
@ -69,12 +69,12 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
// Default scheduler
|
// Default scheduler
|
||||||
private final BukkitScheduler scheduler;
|
private final BukkitScheduler scheduler;
|
||||||
|
|
||||||
// Our protocol manager
|
|
||||||
private final ProtocolManager manager;
|
|
||||||
|
|
||||||
// Current packet index
|
// Current packet index
|
||||||
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||||
|
|
||||||
|
// Our protocol manager
|
||||||
|
private ProtocolManager manager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a asynchronous filter manager.
|
* Initialize a asynchronous filter manager.
|
||||||
* <p>
|
* <p>
|
||||||
@ -83,7 +83,7 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
* @param scheduler - task scheduler.
|
* @param scheduler - task scheduler.
|
||||||
* @param manager - protocol manager.
|
* @param manager - protocol manager.
|
||||||
*/
|
*/
|
||||||
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) {
|
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
|
||||||
// Initialize timeout listeners
|
// Initialize timeout listeners
|
||||||
this.serverTimeoutListeners = new SortedPacketListenerList();
|
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||||
this.clientTimeoutListeners = new SortedPacketListenerList();
|
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||||
@ -95,12 +95,26 @@ public class AsyncFilterManager implements AsynchronousManager {
|
|||||||
this.playerSendingHandler.initializeScheduler();
|
this.playerSendingHandler.initializeScheduler();
|
||||||
|
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.manager = manager;
|
|
||||||
|
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.mainThread = Thread.currentThread();
|
this.mainThread = Thread.currentThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the protocol manager.
|
||||||
|
* @return The protocol manager.
|
||||||
|
*/
|
||||||
|
public ProtocolManager getManager() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the associated protocol manager.
|
||||||
|
* @param manager - the new manager.
|
||||||
|
*/
|
||||||
|
public void setManager(ProtocolManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||||
return registerAsyncHandler(listener, true);
|
return registerAsyncHandler(listener, true);
|
||||||
|
@ -1,213 +1,213 @@
|
|||||||
/*
|
/*
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
* the License, or (at your option) any later version.
|
* the License, or (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
* See the GNU General Public License for more details.
|
* See the GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program;
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.injector;
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import com.comphenix.protocol.ProtocolLibrary;
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.error.Report;
|
import com.comphenix.protocol.error.Report;
|
||||||
import com.comphenix.protocol.error.ReportType;
|
import com.comphenix.protocol.error.ReportType;
|
||||||
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
import com.comphenix.protocol.injector.PacketConstructor.Unwrapper;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||||
import com.google.common.primitives.Primitives;
|
import com.google.common.primitives.Primitives;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
|
* Represents an object capable of converting wrapped Bukkit objects into NMS objects.
|
||||||
* <p>
|
* <p>
|
||||||
* Typical conversions include:
|
* Typical conversions include:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
|
* <li>org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer</li>
|
||||||
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
|
* <li>org.bukkit.World -> net.minecraft.server.WorldServer</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public class BukkitUnwrapper implements Unwrapper {
|
public class BukkitUnwrapper implements Unwrapper {
|
||||||
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
||||||
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
||||||
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
||||||
|
|
||||||
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
||||||
|
|
||||||
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
private static Map<Class<?>, Unwrapper> unwrapperCache = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
||||||
|
|
||||||
// The current error reporter
|
// The current error reporter
|
||||||
private final ErrorReporter reporter;
|
private final ErrorReporter reporter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
||||||
*/
|
*/
|
||||||
public BukkitUnwrapper() {
|
public BukkitUnwrapper() {
|
||||||
this(ProtocolLibrary.getErrorReporter());
|
this(ProtocolLibrary.getErrorReporter());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new Bukkit unwrapper with the given error reporter.
|
* Construct a new Bukkit unwrapper with the given error reporter.
|
||||||
* @param reporter - the error reporter to use.
|
* @param reporter - the error reporter to use.
|
||||||
*/
|
*/
|
||||||
public BukkitUnwrapper(ErrorReporter reporter) {
|
public BukkitUnwrapper(ErrorReporter reporter) {
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public Object unwrapItem(Object wrappedObject) {
|
public Object unwrapItem(Object wrappedObject) {
|
||||||
// Special case
|
// Special case
|
||||||
if (wrappedObject == null)
|
if (wrappedObject == null)
|
||||||
return null;
|
return null;
|
||||||
Class<?> currentClass = wrappedObject.getClass();
|
Class<?> currentClass = wrappedObject.getClass();
|
||||||
|
|
||||||
// Next, check for types that doesn't have a getHandle()
|
// Next, check for types that doesn't have a getHandle()
|
||||||
if (wrappedObject instanceof Collection) {
|
if (wrappedObject instanceof Collection) {
|
||||||
return handleCollection((Collection<Object>) wrappedObject);
|
return handleCollection((Collection<Object>) wrappedObject);
|
||||||
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
||||||
|
|
||||||
// Retrieve the handle
|
// Retrieve the handle
|
||||||
if (specificUnwrapper != null)
|
if (specificUnwrapper != null)
|
||||||
return specificUnwrapper.unwrapItem(wrappedObject);
|
return specificUnwrapper.unwrapItem(wrappedObject);
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle a collection of items
|
// Handle a collection of items
|
||||||
private Object handleCollection(Collection<Object> wrappedObject) {
|
private Object handleCollection(Collection<Object> wrappedObject) {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
||||||
|
|
||||||
if (copy != null) {
|
if (copy != null) {
|
||||||
// Unwrap every element
|
// Unwrap every element
|
||||||
for (Object element : wrappedObject) {
|
for (Object element : wrappedObject) {
|
||||||
copy.add(unwrapItem(element));
|
copy.add(unwrapItem(element));
|
||||||
}
|
}
|
||||||
return copy;
|
return copy;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Impossible
|
// Impossible
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a cached class unwrapper for the given class.
|
* Retrieve a cached class unwrapper for the given class.
|
||||||
* @param type - the type of the class.
|
* @param type - the type of the class.
|
||||||
* @return An unwrapper for the given class.
|
* @return An unwrapper for the given class.
|
||||||
*/
|
*/
|
||||||
private Unwrapper getSpecificUnwrapper(Class<?> type) {
|
private Unwrapper getSpecificUnwrapper(Class<?> type) {
|
||||||
// See if we're already determined this
|
// See if we're already determined this
|
||||||
if (unwrapperCache.containsKey(type)) {
|
if (unwrapperCache.containsKey(type)) {
|
||||||
// We will never remove from the cache, so this ought to be thread safe
|
// We will never remove from the cache, so this ought to be thread safe
|
||||||
return unwrapperCache.get(type);
|
return unwrapperCache.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Method find = type.getMethod("getHandle");
|
final Method find = type.getMethod("getHandle");
|
||||||
|
|
||||||
// It's thread safe, as getMethod should return the same handle
|
// It's thread safe, as getMethod should return the same handle
|
||||||
Unwrapper methodUnwrapper = new Unwrapper() {
|
Unwrapper methodUnwrapper = new Unwrapper() {
|
||||||
@Override
|
@Override
|
||||||
public Object unwrapItem(Object wrappedObject) {
|
public Object unwrapItem(Object wrappedObject) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return find.invoke(wrappedObject);
|
return find.invoke(wrappedObject);
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
||||||
);
|
);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
// Should not occur either
|
// Should not occur either
|
||||||
return null;
|
return null;
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
// This is really bad
|
// This is really bad
|
||||||
throw new RuntimeException("Minecraft error.", e);
|
throw new RuntimeException("Minecraft error.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
unwrapperCache.put(type, methodUnwrapper);
|
unwrapperCache.put(type, methodUnwrapper);
|
||||||
return methodUnwrapper;
|
return methodUnwrapper;
|
||||||
|
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
|
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
|
||||||
);
|
);
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (NoSuchMethodException e) {
|
||||||
// Try getting the field unwrapper too
|
// Try getting the field unwrapper too
|
||||||
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
||||||
|
|
||||||
if (fieldUnwrapper != null)
|
if (fieldUnwrapper != null)
|
||||||
return fieldUnwrapper;
|
return fieldUnwrapper;
|
||||||
else
|
else
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
|
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default method
|
// Default method
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a cached unwrapper using the handle field.
|
* Retrieve a cached unwrapper using the handle field.
|
||||||
* @param type - a cached field unwrapper.
|
* @param type - a cached field unwrapper.
|
||||||
* @return The cached field unwrapper.
|
* @return The cached field unwrapper.
|
||||||
*/
|
*/
|
||||||
private Unwrapper getFieldUnwrapper(Class<?> type) {
|
private Unwrapper getFieldUnwrapper(Class<?> type) {
|
||||||
final Field find = FieldUtils.getField(type, "handle", true);
|
final Field find = FieldUtils.getField(type, "handle", true);
|
||||||
|
|
||||||
// See if we succeeded
|
// See if we succeeded
|
||||||
if (find != null) {
|
if (find != null) {
|
||||||
Unwrapper fieldUnwrapper = new Unwrapper() {
|
Unwrapper fieldUnwrapper = new Unwrapper() {
|
||||||
@Override
|
@Override
|
||||||
public Object unwrapItem(Object wrappedObject) {
|
public Object unwrapItem(Object wrappedObject) {
|
||||||
try {
|
try {
|
||||||
return FieldUtils.readField(find, wrappedObject, true);
|
return FieldUtils.readField(find, wrappedObject, true);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
|
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find)
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
unwrapperCache.put(type, fieldUnwrapper);
|
unwrapperCache.put(type, fieldUnwrapper);
|
||||||
return fieldUnwrapper;
|
return fieldUnwrapper;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Inform about this too
|
// Inform about this too
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
|
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find)
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,385 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Entity;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.AsynchronousManager;
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
import com.comphenix.protocol.error.Report;
|
||||||
|
import com.comphenix.protocol.error.ReportType;
|
||||||
|
import com.comphenix.protocol.events.ConnectionSide;
|
||||||
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A protocol manager that delays all packet listener registrations and unregistrations until
|
||||||
|
* an underlying protocol manager can be constructed.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public class DelayedPacketManager implements ProtocolManager, InternalManager {
|
||||||
|
// Registering packet IDs that are not supported
|
||||||
|
public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s.");
|
||||||
|
public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s.");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a packet that will be transmitted later.
|
||||||
|
* @author Kristian
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static class QueuedPacket {
|
||||||
|
private final Player player;
|
||||||
|
private final PacketContainer packet;
|
||||||
|
private final boolean filtered;
|
||||||
|
private final ConnectionSide side;
|
||||||
|
|
||||||
|
public QueuedPacket(Player player, PacketContainer packet, boolean filtered, ConnectionSide side) {
|
||||||
|
this.player = player;
|
||||||
|
this.packet = packet;
|
||||||
|
this.filtered = filtered;
|
||||||
|
this.side = side;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the packet that will be transmitted or receieved.
|
||||||
|
* @return The packet.
|
||||||
|
*/
|
||||||
|
public PacketContainer getPacket() {
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the player that will send or recieve the packet.
|
||||||
|
* @return The source.
|
||||||
|
*/
|
||||||
|
public Player getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve whether or not the packet will the sent or received.
|
||||||
|
* @return The connection side.
|
||||||
|
*/
|
||||||
|
public ConnectionSide getSide() {
|
||||||
|
return side;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the packet should be intercepted by packet listeners.
|
||||||
|
* @return TRUE if it should, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isFiltered() {
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private volatile InternalManager delegate;
|
||||||
|
|
||||||
|
// Packet listeners that will be registered
|
||||||
|
private final Set<PacketListener> queuedListeners = Sets.newSetFromMap(Maps.<PacketListener, Boolean>newConcurrentMap());
|
||||||
|
private final List<QueuedPacket> queuedPackets = Collections.synchronizedList(Lists.<QueuedPacket>newArrayList());
|
||||||
|
|
||||||
|
private AsynchronousManager asyncManager;
|
||||||
|
private ErrorReporter reporter;
|
||||||
|
|
||||||
|
// The current hook
|
||||||
|
private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||||
|
|
||||||
|
// If we have been closed
|
||||||
|
private boolean closed;
|
||||||
|
|
||||||
|
// Queued registration
|
||||||
|
private PluginManager queuedManager;
|
||||||
|
private Plugin queuedPlugin;
|
||||||
|
|
||||||
|
public DelayedPacketManager(@Nonnull ErrorReporter reporter) {
|
||||||
|
Preconditions.checkNotNull(reporter, "reporter cannot be NULL.");
|
||||||
|
|
||||||
|
this.reporter = reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying protocol manager.
|
||||||
|
* @return The underlying manager.
|
||||||
|
*/
|
||||||
|
public InternalManager getDelegate() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the delegate to the underlying manager.
|
||||||
|
* <p>
|
||||||
|
* This will prompt this packet manager to immediately transmit and
|
||||||
|
* register all queued packets an listeners.
|
||||||
|
* @param delegate - delegate to the new manager.
|
||||||
|
*/
|
||||||
|
protected void setDelegate(InternalManager delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
|
||||||
|
if (delegate != null) {
|
||||||
|
// Update the hook if needed
|
||||||
|
if (!Objects.equal(delegate.getPlayerHook(), hook)) {
|
||||||
|
delegate.setPlayerHook(hook);
|
||||||
|
}
|
||||||
|
// Register events as well
|
||||||
|
if (queuedManager != null && queuedPlugin != null) {
|
||||||
|
delegate.registerEvents(queuedManager, queuedPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PacketListener listener : queuedListeners) {
|
||||||
|
try {
|
||||||
|
delegate.addPacketListener(listener);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Inform about this plugin error
|
||||||
|
reporter.reportWarning(this,
|
||||||
|
Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER).
|
||||||
|
callerParam(delegate).messageParam(listener).error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (queuedPackets) {
|
||||||
|
for (QueuedPacket packet : queuedPackets) {
|
||||||
|
try {
|
||||||
|
// Attempt to send it now
|
||||||
|
switch (packet.getSide()) {
|
||||||
|
case CLIENT_SIDE:
|
||||||
|
delegate.recieveClientPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered());
|
||||||
|
break;
|
||||||
|
case SERVER_SIDE:
|
||||||
|
delegate.sendServerPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Inform about this plugin error
|
||||||
|
reporter.reportWarning(this,
|
||||||
|
Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET).
|
||||||
|
callerParam(delegate).messageParam(packet).error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't keep this around anymore
|
||||||
|
queuedListeners.clear();
|
||||||
|
queuedPackets.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||||
|
this.hook = playerHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlayerInjectHooks getPlayerHook() {
|
||||||
|
return hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
|
||||||
|
sendServerPacket(reciever, packet, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.sendServerPacket(reciever, packet, filters);
|
||||||
|
} else {
|
||||||
|
queuedPackets.add(new QueuedPacket(reciever, packet, filters, ConnectionSide.SERVER_SIDE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
|
||||||
|
recieveClientPacket(sender, packet, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.recieveClientPacket(sender, packet, filters);
|
||||||
|
} else {
|
||||||
|
queuedPackets.add(new QueuedPacket(sender, packet, filters, ConnectionSide.CLIENT_SIDE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableSet<PacketListener> getPacketListeners() {
|
||||||
|
if (delegate != null)
|
||||||
|
return delegate.getPacketListeners();
|
||||||
|
else
|
||||||
|
return ImmutableSet.copyOf(queuedListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPacketListener(PacketListener listener) {
|
||||||
|
if (delegate != null)
|
||||||
|
delegate.addPacketListener(listener);
|
||||||
|
else
|
||||||
|
queuedListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePacketListener(PacketListener listener) {
|
||||||
|
if (delegate != null)
|
||||||
|
delegate.removePacketListener(listener);
|
||||||
|
else
|
||||||
|
queuedListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePacketListeners(Plugin plugin) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.removePacketListeners(plugin);
|
||||||
|
} else {
|
||||||
|
for (Iterator<PacketListener> it = queuedListeners.iterator(); it.hasNext(); ) {
|
||||||
|
// Remove listeners of the same plugin
|
||||||
|
if (Objects.equal(it.next().getPlugin(), plugin)) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketContainer createPacket(int id) {
|
||||||
|
if (delegate != null)
|
||||||
|
return delegate.createPacket(id);
|
||||||
|
return createPacket(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketContainer createPacket(int id, boolean forceDefaults) {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.createPacket(id);
|
||||||
|
} else {
|
||||||
|
// Fallback implementation
|
||||||
|
PacketContainer packet = new PacketContainer(id);
|
||||||
|
|
||||||
|
// Use any default values if possible
|
||||||
|
if (forceDefaults) {
|
||||||
|
try {
|
||||||
|
packet.getModifier().writeDefaults();
|
||||||
|
} catch (FieldAccessException e) {
|
||||||
|
throw new RuntimeException("Security exception.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
|
||||||
|
if (delegate != null)
|
||||||
|
return delegate.createPacketConstructor(id, arguments);
|
||||||
|
else
|
||||||
|
return PacketConstructor.DEFAULT.withPacket(id, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Integer> getSendingFilters() {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.getSendingFilters();
|
||||||
|
} else {
|
||||||
|
// Linear scan is fast enough here
|
||||||
|
Set<Integer> sending = Sets.newHashSet();
|
||||||
|
|
||||||
|
for (PacketListener listener : queuedListeners) {
|
||||||
|
sending.addAll(listener.getSendingWhitelist().getWhitelist());
|
||||||
|
}
|
||||||
|
return sending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Integer> getReceivingFilters() {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.getReceivingFilters();
|
||||||
|
} else {
|
||||||
|
Set<Integer> recieving = Sets.newHashSet();
|
||||||
|
|
||||||
|
for (PacketListener listener : queuedListeners) {
|
||||||
|
recieving.addAll(listener.getReceivingWhitelist().getWhitelist());
|
||||||
|
}
|
||||||
|
return recieving;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||||
|
if (delegate != null)
|
||||||
|
delegate.updateEntity(entity, observers);
|
||||||
|
else
|
||||||
|
EntityUtilities.updateEntity(entity, observers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
|
||||||
|
if (delegate != null)
|
||||||
|
return delegate.getEntityFromID(container, id);
|
||||||
|
else
|
||||||
|
return EntityUtilities.getEntityFromID(container, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
|
||||||
|
if (delegate != null)
|
||||||
|
return delegate.getEntityTrackers(entity);
|
||||||
|
else
|
||||||
|
return EntityUtilities.getEntityTrackers(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed || (delegate != null && delegate.isClosed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AsynchronousManager getAsynchronousManager() {
|
||||||
|
if (delegate != null)
|
||||||
|
return delegate.getAsynchronousManager();
|
||||||
|
else
|
||||||
|
return asyncManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsynchronousManager(AsynchronousManager asyncManager) {
|
||||||
|
this.asyncManager = asyncManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerEvents(PluginManager manager, Plugin plugin) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.registerEvents(manager, plugin);
|
||||||
|
} else {
|
||||||
|
queuedManager = manager;
|
||||||
|
queuedPlugin = plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (delegate != null)
|
||||||
|
delegate.close();
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
|
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields access to the internal hook configuration.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public interface InternalManager extends ProtocolManager {
|
||||||
|
/**
|
||||||
|
* Retrieves how the server packets are read.
|
||||||
|
* @return Injection method for reading server packets.
|
||||||
|
*/
|
||||||
|
public PlayerInjectHooks getPlayerHook();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets how the server packets are read.
|
||||||
|
* @param playerHook - the new injection method for reading server packets.
|
||||||
|
*/
|
||||||
|
public void setPlayerHook(PlayerInjectHooks playerHook);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register this protocol manager on Bukkit.
|
||||||
|
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||||
|
* @param plugin - the parent plugin.
|
||||||
|
*/
|
||||||
|
public void registerEvents(PluginManager manager, final Plugin plugin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when ProtocolLib is closing.
|
||||||
|
*/
|
||||||
|
public void close();
|
||||||
|
}
|
@ -1,68 +1,68 @@
|
|||||||
/*
|
/*
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
* the License, or (at your option) any later version.
|
* the License, or (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
* See the GNU General Public License for more details.
|
* See the GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program;
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.injector;
|
package com.comphenix.protocol.injector;
|
||||||
|
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
import com.comphenix.protocol.events.PacketEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an object that initiate the packet listeners.
|
* Represents an object that initiate the packet listeners.
|
||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
public interface ListenerInvoker {
|
public interface ListenerInvoker {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes the given packet event for every registered listener.
|
* Invokes the given packet event for every registered listener.
|
||||||
* @param event - the packet event to invoke.
|
* @param event - the packet event to invoke.
|
||||||
*/
|
*/
|
||||||
public abstract void invokePacketRecieving(PacketEvent event);
|
public abstract void invokePacketRecieving(PacketEvent event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes the given packet event for every registered listener.
|
* Invokes the given packet event for every registered listener.
|
||||||
* @param event - the packet event to invoke.
|
* @param event - the packet event to invoke.
|
||||||
*/
|
*/
|
||||||
public abstract void invokePacketSending(PacketEvent event);
|
public abstract void invokePacketSending(PacketEvent event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the associated ID of a packet.
|
* Retrieve the associated ID of a packet.
|
||||||
* @param packet - the packet.
|
* @param packet - the packet.
|
||||||
* @return The packet ID.
|
* @return The packet ID.
|
||||||
*/
|
*/
|
||||||
public abstract int getPacketID(Object packet);
|
public abstract int getPacketID(Object packet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associate a given class with the given packet ID. Internal method.
|
* Associate a given class with the given packet ID. Internal method.
|
||||||
* @param clazz - class to associate.
|
* @param clazz - class to associate.
|
||||||
*/
|
*/
|
||||||
public abstract void unregisterPacketClass(Class<?> clazz);
|
public abstract void unregisterPacketClass(Class<?> clazz);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a given class in the packet registry. Internal method.
|
* Register a given class in the packet registry. Internal method.
|
||||||
* @param clazz - class to register.
|
* @param clazz - class to register.
|
||||||
* @param packetID - the the new associated packet ID.
|
* @param packetID - the the new associated packet ID.
|
||||||
*/
|
*/
|
||||||
public abstract void registerPacketClass(Class<?> clazz, int packetID);
|
public abstract void registerPacketClass(Class<?> clazz, int packetID);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the correct packet class from a given packet ID.
|
* Retrieves the correct packet class from a given packet ID.
|
||||||
* @param packetID - the packet ID.
|
* @param packetID - the packet ID.
|
||||||
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
* @param forceVanilla - whether or not to look for vanilla classes, not injected classes.
|
||||||
* @return The associated class.
|
* @return The associated class.
|
||||||
*/
|
*/
|
||||||
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
|
public abstract Class<?> getPacketClassFromID(int packetID, boolean forceVanilla);
|
||||||
}
|
}
|
@ -42,9 +42,11 @@ import org.bukkit.event.Listener;
|
|||||||
import org.bukkit.event.player.PlayerJoinEvent;
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
import org.bukkit.event.player.PlayerQuitEvent;
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
import org.bukkit.event.server.PluginDisableEvent;
|
import org.bukkit.event.server.PluginDisableEvent;
|
||||||
|
import org.bukkit.event.world.WorldInitEvent;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
|
||||||
|
import com.comphenix.executors.BukkitFutures;
|
||||||
import com.comphenix.protocol.AsynchronousManager;
|
import com.comphenix.protocol.AsynchronousManager;
|
||||||
import com.comphenix.protocol.ProtocolManager;
|
import com.comphenix.protocol.ProtocolManager;
|
||||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||||
@ -56,6 +58,7 @@ import com.comphenix.protocol.events.*;
|
|||||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||||
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
|
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
|
||||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||||
|
import com.comphenix.protocol.injector.player.InjectedServerConnection;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
|
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
||||||
@ -67,8 +70,11 @@ import com.comphenix.protocol.utility.MinecraftVersion;
|
|||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
|
||||||
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
|
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker, InternalManager {
|
||||||
|
|
||||||
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
|
public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list.");
|
||||||
public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
|
public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
|
||||||
|
|
||||||
@ -87,6 +93,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin.");
|
public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin.");
|
||||||
public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s");
|
public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s");
|
||||||
|
|
||||||
|
public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event.");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the inject hook type. Different types allow for maximum compatibility.
|
* Sets the inject hook type. Different types allow for maximum compatibility.
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
@ -173,23 +181,31 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
/**
|
/**
|
||||||
* Only create instances of this class if protocol lib is disabled.
|
* Only create instances of this class if protocol lib is disabled.
|
||||||
*/
|
*/
|
||||||
public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, DelayedSingleTask unhookTask, ErrorReporter reporter) {
|
public PacketFilterManager(
|
||||||
this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter);
|
ClassLoader classLoader, Server server, Plugin library,
|
||||||
}
|
AsyncFilterManager asyncManager, MinecraftVersion mcVersion,
|
||||||
|
final DelayedSingleTask unhookTask,
|
||||||
/**
|
ErrorReporter reporter, boolean nettyEnabled) {
|
||||||
* Only create instances of this class if protocol lib is disabled.
|
|
||||||
*/
|
|
||||||
public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library,
|
|
||||||
MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) {
|
|
||||||
|
|
||||||
if (reporter == null)
|
if (reporter == null)
|
||||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||||
if (classLoader == null)
|
if (classLoader == null)
|
||||||
throw new IllegalArgumentException("classLoader cannot be NULL.");
|
throw new IllegalArgumentException("classLoader cannot be NULL.");
|
||||||
|
|
||||||
// Just boilerplate
|
// Used to determine if injection is needed
|
||||||
final DelayedSingleTask finalUnhookTask = unhookTask;
|
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(@Nullable GamePhase phase) {
|
||||||
|
boolean result = true;
|
||||||
|
|
||||||
|
if (phase.hasLogin())
|
||||||
|
result &= getPhaseLoginCount() > 0;
|
||||||
|
// Note that we will still hook players if the unhooking has been delayed
|
||||||
|
if (phase.hasPlaying())
|
||||||
|
result &= getPhasePlayingCount() > 0 || unhookTask.isRunning();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Listener containers
|
// Listener containers
|
||||||
this.recievedListeners = new SortedPacketListenerList();
|
this.recievedListeners = new SortedPacketListenerList();
|
||||||
@ -204,70 +220,99 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
// The plugin verifier
|
// The plugin verifier
|
||||||
this.pluginVerifier = new PluginVerifier(library);
|
this.pluginVerifier = new PluginVerifier(library);
|
||||||
|
|
||||||
// Used to determine if injection is needed
|
// Use the correct injection type
|
||||||
Predicate<GamePhase> isInjectionNecessary = new Predicate<GamePhase>() {
|
if (nettyEnabled) {
|
||||||
@Override
|
spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
|
||||||
public boolean apply(@Nullable GamePhase phase) {
|
this.playerInjection = spigotInjector.getPlayerHandler();
|
||||||
boolean result = true;
|
this.packetInjector = spigotInjector.getPacketInjector();
|
||||||
|
|
||||||
if (phase.hasLogin())
|
} else {
|
||||||
result &= getPhaseLoginCount() > 0;
|
// Initialize standard injection mangers
|
||||||
// Note that we will still hook players if the unhooking has been delayed
|
this.playerInjection = PlayerInjectorBuilder.newBuilder().
|
||||||
if (phase.hasPlaying())
|
invoker(this).
|
||||||
result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning();
|
server(server).
|
||||||
return result;
|
reporter(reporter).
|
||||||
}
|
classLoader(classLoader).
|
||||||
};
|
packetListeners(packetListeners).
|
||||||
|
injectionFilter(isInjectionNecessary).
|
||||||
|
version(mcVersion).
|
||||||
|
buildHandler();
|
||||||
|
|
||||||
|
this.packetInjector = PacketInjectorBuilder.newBuilder().
|
||||||
|
invoker(this).
|
||||||
|
reporter(reporter).
|
||||||
|
classLoader(classLoader).
|
||||||
|
playerInjection(playerInjection).
|
||||||
|
buildInjector();
|
||||||
|
}
|
||||||
|
this.asyncFilterManager = asyncManager;
|
||||||
|
|
||||||
|
// Attempt to load the list of server and client packets
|
||||||
try {
|
try {
|
||||||
// Spigot
|
knowsServerPackets = PacketRegistry.getServerPackets() != null;
|
||||||
if (SpigotPacketInjector.canUseSpigotListener()) {
|
knowsClientPackets = PacketRegistry.getClientPackets() != null;
|
||||||
spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server);
|
|
||||||
this.playerInjection = spigotInjector.getPlayerHandler();
|
|
||||||
this.packetInjector = spigotInjector.getPacketInjector();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Initialize standard injection mangers
|
|
||||||
this.playerInjection = PlayerInjectorBuilder.newBuilder().
|
|
||||||
invoker(this).
|
|
||||||
server(server).
|
|
||||||
reporter(reporter).
|
|
||||||
classLoader(classLoader).
|
|
||||||
packetListeners(packetListeners).
|
|
||||||
injectionFilter(isInjectionNecessary).
|
|
||||||
version(mcVersion).
|
|
||||||
buildHandler();
|
|
||||||
|
|
||||||
this.packetInjector = PacketInjectorBuilder.newBuilder().
|
|
||||||
invoker(this).
|
|
||||||
reporter(reporter).
|
|
||||||
classLoader(classLoader).
|
|
||||||
playerInjection(playerInjection).
|
|
||||||
buildInjector();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
|
|
||||||
|
|
||||||
// Attempt to load the list of server and client packets
|
|
||||||
try {
|
|
||||||
knowsServerPackets = PacketRegistry.getServerPackets() != null;
|
|
||||||
knowsClientPackets = PacketRegistry.getClientPackets() != null;
|
|
||||||
} catch (FieldAccessException e) {
|
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (FieldAccessException e) {
|
} catch (FieldAccessException e) {
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e));
|
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static InternalManager createManager(
|
||||||
* Initiate logic that is performed after the world has loaded.
|
final ClassLoader classLoader, final Server server, final Plugin library,
|
||||||
*/
|
final MinecraftVersion mcVersion, final DelayedSingleTask unhookTask,
|
||||||
public void postWorldLoaded() {
|
final ErrorReporter reporter) {
|
||||||
playerInjection.postWorldLoaded();
|
|
||||||
|
final AsyncFilterManager asyncManager = new AsyncFilterManager(reporter, server.getScheduler());
|
||||||
|
|
||||||
|
// Spigot
|
||||||
|
if (SpigotPacketInjector.canUseSpigotListener()) {
|
||||||
|
// We need to delay this until we know if Netty is enabled
|
||||||
|
final DelayedPacketManager delayed = new DelayedPacketManager(reporter);
|
||||||
|
|
||||||
|
Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback<WorldInitEvent>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(WorldInitEvent event) {
|
||||||
|
// Nevermind
|
||||||
|
if (delayed.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Now we are probably able to check for Netty
|
||||||
|
InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null);
|
||||||
|
Object connection = inspector.getServerConnection();
|
||||||
|
|
||||||
|
// Use netty if we have a non-standard ServerConnection class
|
||||||
|
boolean useNetty = !MinecraftReflection.isMinecraftObject(connection);
|
||||||
|
|
||||||
|
// Switch to the standard manager
|
||||||
|
delayed.setDelegate(new PacketFilterManager(
|
||||||
|
classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, useNetty)
|
||||||
|
);
|
||||||
|
// Reference this manager directly
|
||||||
|
asyncManager.setManager(delayed.getDelegate());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
onFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable error) {
|
||||||
|
reporter.reportWarning(this, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Let plugins use this version instead
|
||||||
|
return delayed;
|
||||||
|
} else {
|
||||||
|
// The standard manager
|
||||||
|
PacketFilterManager manager = new PacketFilterManager(
|
||||||
|
classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, false);
|
||||||
|
|
||||||
|
asyncManager.setManager(manager);
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AsynchronousManager getAsynchronousManager() {
|
public AsynchronousManager getAsynchronousManager() {
|
||||||
return asyncFilterManager;
|
return asyncFilterManager;
|
||||||
@ -277,6 +322,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
* Retrieves how the server packets are read.
|
* Retrieves how the server packets are read.
|
||||||
* @return Injection method for reading server packets.
|
* @return Injection method for reading server packets.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public PlayerInjectHooks getPlayerHook() {
|
public PlayerInjectHooks getPlayerHook() {
|
||||||
return playerInjection.getPlayerHook();
|
return playerInjection.getPlayerHook();
|
||||||
}
|
}
|
||||||
@ -285,6 +331,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
* Sets how the server packets are read.
|
* Sets how the server packets are read.
|
||||||
* @param playerHook - the new injection method for reading server packets.
|
* @param playerHook - the new injection method for reading server packets.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||||
playerInjection.setPlayerHook(playerHook);
|
playerInjection.setPlayerHook(playerHook);
|
||||||
}
|
}
|
||||||
@ -707,6 +754,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||||
* @param plugin - the parent plugin.
|
* @param plugin - the parent plugin.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void registerEvents(PluginManager manager, final Plugin plugin) {
|
public void registerEvents(PluginManager manager, final Plugin plugin) {
|
||||||
if (spigotInjector != null && !spigotInjector.register(plugin))
|
if (spigotInjector != null && !spigotInjector.register(plugin))
|
||||||
throw new IllegalArgumentException("Spigot has already been registered.");
|
throw new IllegalArgumentException("Spigot has already been registered.");
|
||||||
@ -974,9 +1022,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
return hasClosed;
|
return hasClosed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Called when ProtocolLib is closing.
|
|
||||||
*/
|
|
||||||
public void close() {
|
public void close() {
|
||||||
// Guard
|
// Guard
|
||||||
if (hasClosed)
|
if (hasClosed)
|
||||||
|
@ -1,338 +1,416 @@
|
|||||||
/*
|
/*
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
* the License, or (at your option) any later version.
|
* the License, or (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
* See the GNU General Public License for more details.
|
* See the GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program;
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.injector.player;
|
package com.comphenix.protocol.injector.player;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import net.sf.cglib.proxy.Factory;
|
|
||||||
|
import net.sf.cglib.proxy.Factory;
|
||||||
import org.bukkit.Server;
|
|
||||||
|
import org.bukkit.Server;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
|
||||||
import com.comphenix.protocol.error.Report;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.error.ReportType;
|
import com.comphenix.protocol.error.Report;
|
||||||
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
import com.comphenix.protocol.error.ReportType;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.injector.server.AbstractInputStreamLookup;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.ObjectWriter;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.reflect.VolatileField;
|
import com.comphenix.protocol.reflect.ObjectWriter;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.reflect.VolatileField;
|
||||||
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
/**
|
|
||||||
* Used to ensure that the 1.3 server is referencing the correct server handler.
|
/**
|
||||||
*
|
* Used to ensure that the 1.3 server is referencing the correct server handler.
|
||||||
* @author Kristian
|
*
|
||||||
*/
|
* @author Kristian
|
||||||
class InjectedServerConnection {
|
*/
|
||||||
// A number of things can go wrong ...
|
public class InjectedServerConnection {
|
||||||
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
|
// A number of things can go wrong ...
|
||||||
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
|
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit.");
|
||||||
|
public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
|
||||||
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
|
|
||||||
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
|
public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer.");
|
||||||
|
public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread.");
|
||||||
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
|
|
||||||
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
|
public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection");
|
||||||
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
|
public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s");
|
||||||
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
|
public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread.");
|
||||||
|
public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s.");
|
||||||
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
|
|
||||||
|
public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new.");
|
||||||
private static Field listenerThreadField;
|
|
||||||
private static Field minecraftServerField;
|
private static Field listenerThreadField;
|
||||||
private static Field listField;
|
private static Field minecraftServerField;
|
||||||
private static Field dedicatedThreadField;
|
private static Field listField;
|
||||||
|
private static Field dedicatedThreadField;
|
||||||
private static Method serverConnectionMethod;
|
|
||||||
|
private static Method serverConnectionMethod;
|
||||||
private List<VolatileField> listFields;
|
|
||||||
private List<ReplacedArrayList<Object>> replacedLists;
|
private List<VolatileField> listFields;
|
||||||
|
private List<ReplacedArrayList<Object>> replacedLists;
|
||||||
// Used to inject net handlers
|
|
||||||
private NetLoginInjector netLoginInjector;
|
// The current detected server socket
|
||||||
|
public enum ServerSocketType {
|
||||||
// Inject server connections
|
SERVER_CONNECTION,
|
||||||
private AbstractInputStreamLookup socketInjector;
|
LISTENER_THREAD,
|
||||||
|
}
|
||||||
private Server server;
|
|
||||||
private ErrorReporter reporter;
|
// Used to inject net handlers
|
||||||
private boolean hasAttempted;
|
private NetLoginInjector netLoginInjector;
|
||||||
private boolean hasSuccess;
|
|
||||||
|
// Inject server connections
|
||||||
private Object minecraftServer = null;
|
private AbstractInputStreamLookup socketInjector;
|
||||||
|
|
||||||
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
|
// Detected by the initializer
|
||||||
this.listFields = new ArrayList<VolatileField>();
|
private ServerSocketType socketType;
|
||||||
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
|
||||||
this.reporter = reporter;
|
private Server server;
|
||||||
this.server = server;
|
private ErrorReporter reporter;
|
||||||
this.socketInjector = socketInjector;
|
private boolean hasAttempted;
|
||||||
this.netLoginInjector = netLoginInjector;
|
private boolean hasSuccess;
|
||||||
}
|
|
||||||
|
private Object minecraftServer = null;
|
||||||
public void injectList() {
|
|
||||||
// Only execute this method once
|
public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) {
|
||||||
if (!hasAttempted)
|
this.listFields = new ArrayList<VolatileField>();
|
||||||
hasAttempted = true;
|
this.replacedLists = new ArrayList<ReplacedArrayList<Object>>();
|
||||||
else
|
this.reporter = reporter;
|
||||||
return;
|
this.server = server;
|
||||||
|
this.socketInjector = socketInjector;
|
||||||
if (minecraftServerField == null)
|
this.netLoginInjector = netLoginInjector;
|
||||||
minecraftServerField = FuzzyReflection.fromObject(server, true).
|
}
|
||||||
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
|
|
||||||
|
/**
|
||||||
try {
|
* Initial reflective detective work. Will be automatically called by most methods in this class.
|
||||||
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
*/
|
||||||
} catch (IllegalAccessException e1) {
|
public void initialize() {
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
|
// Only execute this method once
|
||||||
return;
|
if (!hasAttempted)
|
||||||
}
|
hasAttempted = true;
|
||||||
|
else
|
||||||
try {
|
return;
|
||||||
if (serverConnectionMethod == null)
|
|
||||||
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
if (minecraftServerField == null)
|
||||||
getMethodByParameters("getServerConnection",
|
minecraftServerField = FuzzyReflection.fromObject(server, true).
|
||||||
MinecraftReflection.getServerConnectionClass(), new Class[] {});
|
getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass());
|
||||||
// We're using Minecraft 1.3.1
|
|
||||||
injectServerConnection();
|
try {
|
||||||
|
minecraftServer = FieldUtils.readField(minecraftServerField, server, true);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalAccessException e1) {
|
||||||
|
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER));
|
||||||
// Minecraft 1.2.5 or lower
|
return;
|
||||||
injectListenerThread();
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
try {
|
||||||
// Oh damn - inform the player
|
if (serverConnectionMethod == null)
|
||||||
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
|
serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()).
|
||||||
}
|
getMethodByParameters("getServerConnection",
|
||||||
}
|
MinecraftReflection.getServerConnectionClass(), new Class[] {});
|
||||||
|
// We're using Minecraft 1.3.1
|
||||||
private void injectListenerThread() {
|
socketType = ServerSocketType.SERVER_CONNECTION;
|
||||||
try {
|
|
||||||
if (listenerThreadField == null)
|
} catch (IllegalArgumentException e) {
|
||||||
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
|
// Minecraft 1.2.5 or lower
|
||||||
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
|
socketType = ServerSocketType.LISTENER_THREAD;
|
||||||
} catch (RuntimeException e) {
|
|
||||||
reporter.reportDetailed(this,
|
} catch (Exception e) {
|
||||||
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
|
// Oh damn - inform the player
|
||||||
);
|
reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e));
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object listenerThread = null;
|
/**
|
||||||
|
* Retrieve the known server socket type.
|
||||||
// Attempt to get the thread
|
* <p>
|
||||||
try {
|
* This depends on the version of CraftBukkit we are using.
|
||||||
listenerThread = listenerThreadField.get(minecraftServer);
|
* @return The server socket type.
|
||||||
} catch (Exception e) {
|
*/
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
|
public ServerSocketType getServerSocketType() {
|
||||||
return;
|
return socketType;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject the server socket too
|
/**
|
||||||
injectServerSocket(listenerThread);
|
* Inject the connection interceptor into the correct server socket implementation.
|
||||||
|
*/
|
||||||
// Just inject every list field we can get
|
public void injectList() {
|
||||||
injectEveryListField(listenerThread, 1);
|
initialize();
|
||||||
hasSuccess = true;
|
|
||||||
}
|
if (socketType == ServerSocketType.SERVER_CONNECTION) {
|
||||||
|
injectServerConnection();
|
||||||
private void injectServerConnection() {
|
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
|
||||||
Object serverConnection = null;
|
injectListenerThread();
|
||||||
|
} else {
|
||||||
// Careful - we might fail
|
// Damn it
|
||||||
try {
|
throw new IllegalStateException("Unable to detected server connection.");
|
||||||
serverConnection = serverConnectionMethod.invoke(minecraftServer);
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
reporter.reportDetailed(this,
|
|
||||||
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
|
/**
|
||||||
);
|
* Retrieve the listener thread field.
|
||||||
return;
|
*/
|
||||||
}
|
private void initializeListenerField() {
|
||||||
|
if (listenerThreadField == null)
|
||||||
if (listField == null)
|
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
|
||||||
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
|
||||||
getFieldByType("netServerHandlerList", List.class);
|
}
|
||||||
if (dedicatedThreadField == null) {
|
|
||||||
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
|
/**
|
||||||
getFieldListByType(Thread.class);
|
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
|
||||||
|
* @return The listener thread, or NULL.
|
||||||
// Verify the field count
|
* @throws IllegalAccessException Cannot access field.
|
||||||
if (matches.size() != 1)
|
* @hrows RuntimeException Unexpected class structure - the field doesn't exist.
|
||||||
reporter.reportWarning(this,
|
*/
|
||||||
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
|
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
|
||||||
);
|
initialize();
|
||||||
else
|
|
||||||
dedicatedThreadField = matches.get(0);
|
if (socketType == ServerSocketType.LISTENER_THREAD) {
|
||||||
}
|
initializeListenerField();
|
||||||
|
return listenerThreadField.get(minecraftServer);
|
||||||
// Next, try to get the dedicated thread
|
} else {
|
||||||
try {
|
return null;
|
||||||
if (dedicatedThreadField != null) {
|
}
|
||||||
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
|
}
|
||||||
|
|
||||||
// Inject server socket and NetServerHandlers.
|
/**
|
||||||
injectServerSocket(dedicatedThread);
|
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
|
||||||
injectEveryListField(dedicatedThread, 1);
|
* @return The socket connection, or NULL.
|
||||||
}
|
* @throws IllegalAccessException If the reflective operation failed.
|
||||||
} catch (IllegalAccessException e) {
|
* @throws IllegalArgumentException If the reflective operation failed.
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
|
* @throws InvocationTargetException If the reflective operation failed.
|
||||||
}
|
*/
|
||||||
|
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
|
||||||
injectIntoList(serverConnection, listField);
|
initialize();
|
||||||
hasSuccess = true;
|
|
||||||
}
|
if (socketType == ServerSocketType.SERVER_CONNECTION)
|
||||||
|
return serverConnectionMethod.invoke(minecraftServer);
|
||||||
private void injectServerSocket(Object container) {
|
else
|
||||||
socketInjector.inject(container);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void injectListenerThread() {
|
||||||
* Automatically inject into every List-compatible public or private field of the given object.
|
try {
|
||||||
* @param container - container object with the fields to inject.
|
initializeListenerField();
|
||||||
* @param minimum - the minimum number of fields we expect exists.
|
} catch (RuntimeException e) {
|
||||||
*/
|
reporter.reportDetailed(this,
|
||||||
private void injectEveryListField(Object container, int minimum) {
|
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
|
||||||
// Ok, great. Get every list field
|
);
|
||||||
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
|
return;
|
||||||
|
}
|
||||||
for (Field list : lists) {
|
|
||||||
injectIntoList(container, list);
|
Object listenerThread = null;
|
||||||
}
|
|
||||||
|
// Attempt to get the thread
|
||||||
// Warn about unexpected errors
|
try {
|
||||||
if (lists.size() < minimum) {
|
listenerThread = getListenerThread();
|
||||||
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
|
} catch (Exception e) {
|
||||||
}
|
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void injectIntoList(Object instance, Field field) {
|
// Inject the server socket too
|
||||||
VolatileField listFieldRef = new VolatileField(field, instance, true);
|
injectServerSocket(listenerThread);
|
||||||
List<Object> list = (List<Object>) listFieldRef.getValue();
|
|
||||||
|
// Just inject every list field we can get
|
||||||
// Careful not to inject twice
|
injectEveryListField(listenerThread, 1);
|
||||||
if (list instanceof ReplacedArrayList) {
|
hasSuccess = true;
|
||||||
replacedLists.add((ReplacedArrayList<Object>) list);
|
}
|
||||||
} else {
|
|
||||||
ReplacedArrayList<Object> injectedList = createReplacement(list);
|
private void injectServerConnection() {
|
||||||
|
Object serverConnection = null;
|
||||||
replacedLists.add(injectedList);
|
|
||||||
listFieldRef.setValue(injectedList);
|
// Careful - we might fail
|
||||||
listFields.add(listFieldRef);
|
try {
|
||||||
}
|
serverConnection = getServerConnection();
|
||||||
}
|
} catch (Exception e) {
|
||||||
|
reporter.reportDetailed(this,
|
||||||
// Hack to avoid the "moved to quickly" error
|
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)
|
||||||
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
|
);
|
||||||
return new ReplacedArrayList<Object>(list) {
|
return;
|
||||||
/**
|
}
|
||||||
* Shut up Eclipse!
|
|
||||||
*/
|
if (listField == null)
|
||||||
private static final long serialVersionUID = 2070481080950500367L;
|
listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true).
|
||||||
|
getFieldByType("netServerHandlerList", List.class);
|
||||||
// Object writer we'll use
|
if (dedicatedThreadField == null) {
|
||||||
private final ObjectWriter writer = new ObjectWriter();
|
List<Field> matches = FuzzyReflection.fromObject(serverConnection, true).
|
||||||
|
getFieldListByType(Thread.class);
|
||||||
@Override
|
|
||||||
protected void onReplacing(Object inserting, Object replacement) {
|
// Verify the field count
|
||||||
// Is this a normal Minecraft object?
|
if (matches.size() != 1)
|
||||||
if (!(inserting instanceof Factory)) {
|
reporter.reportWarning(this,
|
||||||
// If so, copy the content of the old element to the new
|
Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size())
|
||||||
try {
|
);
|
||||||
writer.copyTo(inserting, replacement, inserting.getClass());
|
else
|
||||||
} catch (Throwable e) {
|
dedicatedThreadField = matches.get(0);
|
||||||
reporter.reportDetailed(InjectedServerConnection.this,
|
}
|
||||||
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
|
|
||||||
);
|
// Next, try to get the dedicated thread
|
||||||
}
|
try {
|
||||||
}
|
if (dedicatedThreadField != null) {
|
||||||
}
|
Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true);
|
||||||
|
|
||||||
@Override
|
// Inject server socket and NetServerHandlers.
|
||||||
protected void onInserting(Object inserting) {
|
injectServerSocket(dedicatedThread);
|
||||||
// Ready for some login handler injection?
|
injectEveryListField(dedicatedThread, 1);
|
||||||
if (MinecraftReflection.isLoginHandler(inserting)) {
|
}
|
||||||
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
|
} catch (IllegalAccessException e) {
|
||||||
|
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e));
|
||||||
// Only replace if it has changed
|
}
|
||||||
if (inserting != replaced)
|
|
||||||
addMapping(inserting, replaced, true);
|
injectIntoList(serverConnection, listField);
|
||||||
}
|
hasSuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void injectServerSocket(Object container) {
|
||||||
protected void onRemoved(Object removing) {
|
socketInjector.inject(container);
|
||||||
// Clean up?
|
}
|
||||||
if (MinecraftReflection.isLoginHandler(removing)) {
|
|
||||||
netLoginInjector.cleanup(removing);
|
/**
|
||||||
}
|
* Automatically inject into every List-compatible public or private field of the given object.
|
||||||
}
|
* @param container - container object with the fields to inject.
|
||||||
};
|
* @param minimum - the minimum number of fields we expect exists.
|
||||||
}
|
*/
|
||||||
|
private void injectEveryListField(Object container, int minimum) {
|
||||||
/**
|
// Ok, great. Get every list field
|
||||||
* Replace the server handler instance kept by the "keep alive" object.
|
List<Field> lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class);
|
||||||
* @param oldHandler - old server handler.
|
|
||||||
* @param newHandler - new, proxied server handler.
|
for (Field list : lists) {
|
||||||
*/
|
injectIntoList(container, list);
|
||||||
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
}
|
||||||
if (!hasAttempted) {
|
|
||||||
injectList();
|
// Warn about unexpected errors
|
||||||
}
|
if (lists.size() < minimum) {
|
||||||
|
reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass()));
|
||||||
if (hasSuccess) {
|
}
|
||||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
}
|
||||||
replacedList.addMapping(oldHandler, newHandler);
|
|
||||||
}
|
@SuppressWarnings("unchecked")
|
||||||
}
|
private void injectIntoList(Object instance, Field field) {
|
||||||
}
|
VolatileField listFieldRef = new VolatileField(field, instance, true);
|
||||||
|
List<Object> list = (List<Object>) listFieldRef.getValue();
|
||||||
/**
|
|
||||||
* Revert to the old vanilla server handler, if it has been replaced.
|
// Careful not to inject twice
|
||||||
* @param oldHandler - old vanilla server handler.
|
if (list instanceof ReplacedArrayList) {
|
||||||
*/
|
replacedLists.add((ReplacedArrayList<Object>) list);
|
||||||
public void revertServerHandler(Object oldHandler) {
|
} else {
|
||||||
if (hasSuccess) {
|
ReplacedArrayList<Object> injectedList = createReplacement(list);
|
||||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
|
||||||
replacedList.removeMapping(oldHandler);
|
replacedLists.add(injectedList);
|
||||||
}
|
listFieldRef.setValue(injectedList);
|
||||||
}
|
listFields.add(listFieldRef);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Undoes everything.
|
// Hack to avoid the "moved to quickly" error
|
||||||
*/
|
private ReplacedArrayList<Object> createReplacement(List<Object> list) {
|
||||||
public void cleanupAll() {
|
return new ReplacedArrayList<Object>(list) {
|
||||||
if (replacedLists.size() > 0) {
|
/**
|
||||||
// Repair the underlying lists
|
* Shut up Eclipse!
|
||||||
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
*/
|
||||||
replacedList.revertAll();
|
private static final long serialVersionUID = 2070481080950500367L;
|
||||||
}
|
|
||||||
for (VolatileField field : listFields) {
|
// Object writer we'll use
|
||||||
field.revertValue();
|
private final ObjectWriter writer = new ObjectWriter();
|
||||||
}
|
|
||||||
|
@Override
|
||||||
listFields.clear();
|
protected void onReplacing(Object inserting, Object replacement) {
|
||||||
replacedLists.clear();
|
// Is this a normal Minecraft object?
|
||||||
}
|
if (!(inserting instanceof Factory)) {
|
||||||
}
|
// If so, copy the content of the old element to the new
|
||||||
}
|
try {
|
||||||
|
writer.copyTo(inserting, replacement, inserting.getClass());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
reporter.reportDetailed(InjectedServerConnection.this,
|
||||||
|
Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onInserting(Object inserting) {
|
||||||
|
// Ready for some login handler injection?
|
||||||
|
if (MinecraftReflection.isLoginHandler(inserting)) {
|
||||||
|
Object replaced = netLoginInjector.onNetLoginCreated(inserting);
|
||||||
|
|
||||||
|
// Only replace if it has changed
|
||||||
|
if (inserting != replaced)
|
||||||
|
addMapping(inserting, replaced, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemoved(Object removing) {
|
||||||
|
// Clean up?
|
||||||
|
if (MinecraftReflection.isLoginHandler(removing)) {
|
||||||
|
netLoginInjector.cleanup(removing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the server handler instance kept by the "keep alive" object.
|
||||||
|
* @param oldHandler - old server handler.
|
||||||
|
* @param newHandler - new, proxied server handler.
|
||||||
|
*/
|
||||||
|
public void replaceServerHandler(Object oldHandler, Object newHandler) {
|
||||||
|
if (!hasAttempted) {
|
||||||
|
injectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSuccess) {
|
||||||
|
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||||
|
replacedList.addMapping(oldHandler, newHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert to the old vanilla server handler, if it has been replaced.
|
||||||
|
* @param oldHandler - old vanilla server handler.
|
||||||
|
*/
|
||||||
|
public void revertServerHandler(Object oldHandler) {
|
||||||
|
if (hasSuccess) {
|
||||||
|
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||||
|
replacedList.removeMapping(oldHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undoes everything.
|
||||||
|
*/
|
||||||
|
public void cleanupAll() {
|
||||||
|
if (replacedLists.size() > 0) {
|
||||||
|
// Repair the underlying lists
|
||||||
|
for (ReplacedArrayList<Object> replacedList : replacedLists) {
|
||||||
|
replacedList.revertAll();
|
||||||
|
}
|
||||||
|
for (VolatileField field : listFields) {
|
||||||
|
field.revertValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
listFields.clear();
|
||||||
|
replacedLists.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,154 +1,154 @@
|
|||||||
/*
|
/*
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
* the License, or (at your option) any later version.
|
* the License, or (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
* See the GNU General Public License for more details.
|
* See the GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program;
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.injector.player;
|
package com.comphenix.protocol.injector.player;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.error.Report;
|
import com.comphenix.protocol.error.Report;
|
||||||
import com.comphenix.protocol.error.ReportType;
|
import com.comphenix.protocol.error.ReportType;
|
||||||
import com.comphenix.protocol.injector.GamePhase;
|
import com.comphenix.protocol.injector.GamePhase;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
|
||||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injects every NetLoginHandler created by the server.
|
* Injects every NetLoginHandler created by the server.
|
||||||
*
|
*
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
class NetLoginInjector {
|
class NetLoginInjector {
|
||||||
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
|
public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s.");
|
||||||
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
|
public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s.");
|
||||||
|
|
||||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||||
|
|
||||||
// Handles every hook
|
// Handles every hook
|
||||||
private ProxyPlayerInjectionHandler injectionHandler;
|
private ProxyPlayerInjectionHandler injectionHandler;
|
||||||
|
|
||||||
// Create temporary players
|
// Create temporary players
|
||||||
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
|
||||||
|
|
||||||
// The current error reporter
|
// The current error reporter
|
||||||
private ErrorReporter reporter;
|
private ErrorReporter reporter;
|
||||||
private Server server;
|
private Server server;
|
||||||
|
|
||||||
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
|
public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) {
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.injectionHandler = injectionHandler;
|
this.injectionHandler = injectionHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a NetLoginHandler has been created.
|
* Invoked when a NetLoginHandler has been created.
|
||||||
* @param inserting - the new NetLoginHandler.
|
* @param inserting - the new NetLoginHandler.
|
||||||
* @return An injected NetLoginHandler, or the original object.
|
* @return An injected NetLoginHandler, or the original object.
|
||||||
*/
|
*/
|
||||||
public Object onNetLoginCreated(Object inserting) {
|
public Object onNetLoginCreated(Object inserting) {
|
||||||
try {
|
try {
|
||||||
// Make sure we actually need to inject during this phase
|
// Make sure we actually need to inject during this phase
|
||||||
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN))
|
||||||
return inserting;
|
return inserting;
|
||||||
|
|
||||||
Player temporary = playerFactory.createTemporaryPlayer(server);
|
Player temporary = playerFactory.createTemporaryPlayer(server);
|
||||||
// Note that we bail out if there's an existing player injector
|
// Note that we bail out if there's an existing player injector
|
||||||
PlayerInjector injector = injectionHandler.injectPlayer(
|
PlayerInjector injector = injectionHandler.injectPlayer(
|
||||||
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
|
temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN);
|
||||||
|
|
||||||
if (injector != null) {
|
if (injector != null) {
|
||||||
// Update injector as well
|
// Update injector as well
|
||||||
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
|
TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector);
|
||||||
injector.updateOnLogin = true;
|
injector.updateOnLogin = true;
|
||||||
|
|
||||||
// Save the login
|
// Save the login
|
||||||
injectedLogins.putIfAbsent(inserting, injector);
|
injectedLogins.putIfAbsent(inserting, injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
// NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler
|
||||||
return inserting;
|
return inserting;
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Minecraft can't handle this, so we'll deal with it here
|
// Minecraft can't handle this, so we'll deal with it here
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
|
Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER).
|
||||||
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
||||||
callerParam(inserting, injectionHandler).
|
callerParam(inserting, injectionHandler).
|
||||||
error(e)
|
error(e)
|
||||||
);
|
);
|
||||||
return inserting;
|
return inserting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when a NetLoginHandler should be reverted.
|
* Invoked when a NetLoginHandler should be reverted.
|
||||||
* @param inserting - the original NetLoginHandler.
|
* @param inserting - the original NetLoginHandler.
|
||||||
* @return An injected NetLoginHandler, or the original object.
|
* @return An injected NetLoginHandler, or the original object.
|
||||||
*/
|
*/
|
||||||
public synchronized void cleanup(Object removing) {
|
public synchronized void cleanup(Object removing) {
|
||||||
PlayerInjector injected = injectedLogins.get(removing);
|
PlayerInjector injected = injectedLogins.get(removing);
|
||||||
|
|
||||||
if (injected != null) {
|
if (injected != null) {
|
||||||
try {
|
try {
|
||||||
PlayerInjector newInjector = null;
|
PlayerInjector newInjector = null;
|
||||||
Player player = injected.getPlayer();
|
Player player = injected.getPlayer();
|
||||||
|
|
||||||
// Clean up list
|
// Clean up list
|
||||||
injectedLogins.remove(removing);
|
injectedLogins.remove(removing);
|
||||||
|
|
||||||
// No need to clean up twice
|
// No need to clean up twice
|
||||||
if (injected.isClean())
|
if (injected.isClean())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Hack to clean up other references
|
// Hack to clean up other references
|
||||||
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
|
newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager());
|
||||||
injectionHandler.uninjectPlayer(player);
|
injectionHandler.uninjectPlayer(player);
|
||||||
|
|
||||||
// Update NetworkManager
|
// Update NetworkManager
|
||||||
if (newInjector != null) {
|
if (newInjector != null) {
|
||||||
if (injected instanceof NetworkObjectInjector) {
|
if (injected instanceof NetworkObjectInjector) {
|
||||||
newInjector.setNetworkManager(injected.getNetworkManager(), true);
|
newInjector.setNetworkManager(injected.getNetworkManager(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
// Don't leak this to Minecraft
|
// Don't leak this to Minecraft
|
||||||
reporter.reportDetailed(this,
|
reporter.reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
|
Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER).
|
||||||
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
messageParam(MinecraftReflection.getNetLoginHandlerName()).
|
||||||
callerParam(removing).
|
callerParam(removing).
|
||||||
error(e)
|
error(e)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all injected hooks.
|
* Remove all injected hooks.
|
||||||
*/
|
*/
|
||||||
public void cleanupAll() {
|
public void cleanupAll() {
|
||||||
for (PlayerInjector injector : injectedLogins.values()) {
|
for (PlayerInjector injector : injectedLogins.values()) {
|
||||||
injector.cleanupAll();
|
injector.cleanupAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
injectedLogins.clear();
|
injectedLogins.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,9 +161,4 @@ public interface PlayerInjectionHandler {
|
|||||||
* Close any lingering proxy injections.
|
* Close any lingering proxy injections.
|
||||||
*/
|
*/
|
||||||
public abstract void close();
|
public abstract void close();
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform any action that must be delayed until the world(s) has loaded.
|
|
||||||
*/
|
|
||||||
public abstract void postWorldLoaded();
|
|
||||||
}
|
}
|
@ -144,14 +144,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
|||||||
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
|
this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector);
|
||||||
serverInjection.injectList();
|
serverInjection.injectList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postWorldLoaded() {
|
|
||||||
// This will actually create a socket and a seperate thread ...
|
|
||||||
if (inputStreamLookup != null) {
|
|
||||||
inputStreamLookup.postWorldLoaded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves how the server packets are read.
|
* Retrieves how the server packets are read.
|
||||||
|
@ -1,87 +1,82 @@
|
|||||||
package com.comphenix.protocol.injector.server;
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
|
|
||||||
public abstract class AbstractInputStreamLookup {
|
public abstract class AbstractInputStreamLookup {
|
||||||
// Error reporter
|
// Error reporter
|
||||||
protected final ErrorReporter reporter;
|
protected final ErrorReporter reporter;
|
||||||
|
|
||||||
// Reference to the server itself
|
// Reference to the server itself
|
||||||
protected final Server server;
|
protected final Server server;
|
||||||
|
|
||||||
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
|
protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) {
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject the given server thread or dedicated connection.
|
* Inject the given server thread or dedicated connection.
|
||||||
* @param container - class that contains a ServerSocket field.
|
* @param container - class that contains a ServerSocket field.
|
||||||
*/
|
*/
|
||||||
public abstract void inject(Object container);
|
public abstract void inject(Object container);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when the world has loaded.
|
* Retrieve the associated socket injector for a player.
|
||||||
*/
|
* @param input - the indentifying filtered input stream.
|
||||||
public abstract void postWorldLoaded();
|
* @return The socket injector we have associated with this player.
|
||||||
|
*/
|
||||||
/**
|
public abstract SocketInjector waitSocketInjector(InputStream input);
|
||||||
* Retrieve the associated socket injector for a player.
|
|
||||||
* @param input - the indentifying filtered input stream.
|
/**
|
||||||
* @return The socket injector we have associated with this player.
|
* Retrieve an injector by its socket.
|
||||||
*/
|
* @param socket - the socket.
|
||||||
public abstract SocketInjector waitSocketInjector(InputStream input);
|
* @return The socket injector.
|
||||||
|
*/
|
||||||
/**
|
public abstract SocketInjector waitSocketInjector(Socket socket);
|
||||||
* Retrieve an injector by its socket.
|
|
||||||
* @param socket - the socket.
|
/**
|
||||||
* @return The socket injector.
|
* Retrieve a injector by its address.
|
||||||
*/
|
* @param address - the address of the socket.
|
||||||
public abstract SocketInjector waitSocketInjector(Socket socket);
|
* @return The socket injector, or NULL if not found.
|
||||||
|
*/
|
||||||
/**
|
public abstract SocketInjector waitSocketInjector(SocketAddress address);
|
||||||
* Retrieve a injector by its address.
|
|
||||||
* @param address - the address of the socket.
|
/**
|
||||||
* @return The socket injector, or NULL if not found.
|
* Attempt to get a socket injector without blocking the thread.
|
||||||
*/
|
* @param address - the address to lookup.
|
||||||
public abstract SocketInjector waitSocketInjector(SocketAddress address);
|
* @return The socket injector, or NULL if not found.
|
||||||
|
*/
|
||||||
/**
|
public abstract SocketInjector peekSocketInjector(SocketAddress address);
|
||||||
* Attempt to get a socket injector without blocking the thread.
|
|
||||||
* @param address - the address to lookup.
|
/**
|
||||||
* @return The socket injector, or NULL if not found.
|
* Associate a given socket address to the provided socket injector.
|
||||||
*/
|
* @param address - the socket address to associate.
|
||||||
public abstract SocketInjector peekSocketInjector(SocketAddress address);
|
* @param injector - the injector.
|
||||||
|
*/
|
||||||
/**
|
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
|
||||||
* Associate a given socket address to the provided socket injector.
|
|
||||||
* @param address - the socket address to associate.
|
/**
|
||||||
* @param injector - the injector.
|
* If a player can hold a reference to its parent injector, this method will update that reference.
|
||||||
*/
|
* @param previous - the previous injector.
|
||||||
public abstract void setSocketInjector(SocketAddress address, SocketInjector injector);
|
* @param current - the new injector.
|
||||||
|
*/
|
||||||
/**
|
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
|
||||||
* If a player can hold a reference to its parent injector, this method will update that reference.
|
Player player = previous.getPlayer();
|
||||||
* @param previous - the previous injector.
|
|
||||||
* @param current - the new injector.
|
// Default implementation
|
||||||
*/
|
if (player instanceof InjectorContainer) {
|
||||||
protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) {
|
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
|
||||||
Player player = previous.getPlayer();
|
}
|
||||||
|
}
|
||||||
// Default implementation
|
|
||||||
if (player instanceof InjectorContainer) {
|
/**
|
||||||
TemporaryPlayerFactory.setInjectorInPlayer(player, current);
|
* Invoked when the injection should be undone.
|
||||||
}
|
*/
|
||||||
}
|
public abstract void cleanupAll();
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when the injection should be undone.
|
|
||||||
*/
|
|
||||||
public abstract void cleanupAll();
|
|
||||||
}
|
}
|
@ -10,36 +10,14 @@ import java.util.List;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
public class BukkitSocketInjector implements SocketInjector {
|
public class BukkitSocketInjector implements SocketInjector {
|
||||||
/**
|
|
||||||
* Represents a single send packet command.
|
|
||||||
* @author Kristian
|
|
||||||
*/
|
|
||||||
static class SendPacketCommand {
|
|
||||||
private final Object packet;
|
|
||||||
private final boolean filtered;
|
|
||||||
|
|
||||||
public SendPacketCommand(Object packet, boolean filtered) {
|
|
||||||
this.packet = packet;
|
|
||||||
this.filtered = filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getPacket() {
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFiltered() {
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Player player;
|
private Player player;
|
||||||
|
|
||||||
// Queue of server packets
|
// Queue of server packets
|
||||||
private List<SendPacketCommand> syncronizedQueue = Collections.synchronizedList(new ArrayList<SendPacketCommand>());
|
private List<QueuedSendPacket> syncronizedQueue = Collections.synchronizedList(new ArrayList<QueuedSendPacket>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a temporary socket injector.
|
* Represents a temporary socket injector.
|
||||||
* @param temporaryPlayer -
|
* @param temporaryPlayer - a temporary player.
|
||||||
*/
|
*/
|
||||||
public BukkitSocketInjector(Player player) {
|
public BukkitSocketInjector(Player player) {
|
||||||
if (player == null)
|
if (player == null)
|
||||||
@ -65,7 +43,7 @@ public class BukkitSocketInjector implements SocketInjector {
|
|||||||
@Override
|
@Override
|
||||||
public void sendServerPacket(Object packet, boolean filtered)
|
public void sendServerPacket(Object packet, boolean filtered)
|
||||||
throws InvocationTargetException {
|
throws InvocationTargetException {
|
||||||
SendPacketCommand command = new SendPacketCommand(packet, filtered);
|
QueuedSendPacket command = new QueuedSendPacket(packet, filtered);
|
||||||
|
|
||||||
// Queue until we can find something better
|
// Queue until we can find something better
|
||||||
syncronizedQueue.add(command);
|
syncronizedQueue.add(command);
|
||||||
@ -86,7 +64,7 @@ public class BukkitSocketInjector implements SocketInjector {
|
|||||||
// Transmit all queued packets to a different injector.
|
// Transmit all queued packets to a different injector.
|
||||||
try {
|
try {
|
||||||
synchronized(syncronizedQueue) {
|
synchronized(syncronizedQueue) {
|
||||||
for (SendPacketCommand command : syncronizedQueue) {
|
for (QueuedSendPacket command : syncronizedQueue) {
|
||||||
delegate.sendServerPacket(command.getPacket(), command.isFiltered());
|
delegate.sendServerPacket(command.getPacket(), command.isFiltered());
|
||||||
}
|
}
|
||||||
syncronizedQueue.clear();
|
syncronizedQueue.clear();
|
||||||
|
@ -1,191 +1,186 @@
|
|||||||
package com.comphenix.protocol.injector.server;
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
|
|
||||||
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.FieldUtils;
|
import com.comphenix.protocol.reflect.FieldUtils;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.google.common.collect.MapMaker;
|
import com.google.common.collect.MapMaker;
|
||||||
|
|
||||||
class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
class InputStreamReflectLookup extends AbstractInputStreamLookup {
|
||||||
// Used to access the inner input stream of a filtered input stream
|
// Used to access the inner input stream of a filtered input stream
|
||||||
private static Field filteredInputField;
|
private static Field filteredInputField;
|
||||||
|
|
||||||
// The default lookup timeout
|
// The default lookup timeout
|
||||||
private static final long DEFAULT_TIMEOUT = 2000; // ms
|
private static final long DEFAULT_TIMEOUT = 2000; // ms
|
||||||
|
|
||||||
// Using weak keys and values ensures that we will not hold up garbage collection
|
// Using weak keys and values ensures that we will not hold up garbage collection
|
||||||
protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
|
protected BlockingHashMap<SocketAddress, SocketInjector> addressLookup = new BlockingHashMap<SocketAddress, SocketInjector>();
|
||||||
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
|
protected ConcurrentMap<InputStream, SocketAddress> inputLookup = new MapMaker().weakValues().makeMap();
|
||||||
|
|
||||||
// The timeout
|
// The timeout
|
||||||
private final long injectorTimeout;
|
private final long injectorTimeout;
|
||||||
|
|
||||||
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
|
public InputStreamReflectLookup(ErrorReporter reporter, Server server) {
|
||||||
this(reporter, server, DEFAULT_TIMEOUT);
|
this(reporter, server, DEFAULT_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a reflect lookup with a given default injector timeout.
|
* Initialize a reflect lookup with a given default injector timeout.
|
||||||
* <p>
|
* <p>
|
||||||
* This timeout defines the maximum amount of time to wait until an injector has been discovered.
|
* This timeout defines the maximum amount of time to wait until an injector has been discovered.
|
||||||
* @param reporter - the error reporter.
|
* @param reporter - the error reporter.
|
||||||
* @param server - the current Bukkit server.
|
* @param server - the current Bukkit server.
|
||||||
* @param injectorTimeout - the injector timeout.
|
* @param injectorTimeout - the injector timeout.
|
||||||
*/
|
*/
|
||||||
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
|
public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) {
|
||||||
super(reporter, server);
|
super(reporter, server);
|
||||||
this.injectorTimeout = injectorTimeout;
|
this.injectorTimeout = injectorTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inject(Object container) {
|
public void inject(Object container) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postWorldLoaded() {
|
public SocketInjector peekSocketInjector(SocketAddress address) {
|
||||||
// Nothing again
|
try {
|
||||||
}
|
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
@Override
|
// Whatever
|
||||||
public SocketInjector peekSocketInjector(SocketAddress address) {
|
return null;
|
||||||
try {
|
}
|
||||||
return addressLookup.get(address, 0, TimeUnit.MILLISECONDS);
|
}
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// Whatever
|
@Override
|
||||||
return null;
|
public SocketInjector waitSocketInjector(SocketAddress address) {
|
||||||
}
|
try {
|
||||||
}
|
// Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
|
||||||
|
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
|
||||||
@Override
|
// to catch up, so we'll swallow these interrupts.
|
||||||
public SocketInjector waitSocketInjector(SocketAddress address) {
|
//
|
||||||
try {
|
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
|
||||||
// Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to
|
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
|
||||||
// periodically wake up waiting readers and writers. We have to wait for the dedicated server thread
|
} catch (InterruptedException e) {
|
||||||
// to catch up, so we'll swallow these interrupts.
|
// This cannot be!
|
||||||
//
|
throw new IllegalStateException("Impossible exception occured!", e);
|
||||||
// TODO: Consider if we should raise the thread priority of the dedicated server listener thread.
|
}
|
||||||
return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true);
|
}
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// This cannot be!
|
@Override
|
||||||
throw new IllegalStateException("Impossible exception occured!", e);
|
public SocketInjector waitSocketInjector(Socket socket) {
|
||||||
}
|
return waitSocketInjector(socket.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SocketInjector waitSocketInjector(Socket socket) {
|
public SocketInjector waitSocketInjector(InputStream input) {
|
||||||
return waitSocketInjector(socket.getRemoteSocketAddress());
|
try {
|
||||||
}
|
SocketAddress address = waitSocketAddress(input);
|
||||||
|
|
||||||
@Override
|
// Guard against NPE
|
||||||
public SocketInjector waitSocketInjector(InputStream input) {
|
if (address != null)
|
||||||
try {
|
return waitSocketInjector(address);
|
||||||
SocketAddress address = waitSocketAddress(input);
|
else
|
||||||
|
return null;
|
||||||
// Guard against NPE
|
} catch (IllegalAccessException e) {
|
||||||
if (address != null)
|
throw new FieldAccessException("Cannot find or access socket field for " + input, e);
|
||||||
return waitSocketInjector(address);
|
}
|
||||||
else
|
}
|
||||||
return null;
|
|
||||||
} catch (IllegalAccessException e) {
|
/**
|
||||||
throw new FieldAccessException("Cannot find or access socket field for " + input, e);
|
* Use reflection to get the underlying socket address from an input stream.
|
||||||
}
|
* @param stream - the socket stream to lookup.
|
||||||
}
|
* @return The underlying socket address, or NULL if not found.
|
||||||
|
* @throws IllegalAccessException Unable to access socket field.
|
||||||
/**
|
*/
|
||||||
* Use reflection to get the underlying socket address from an input stream.
|
private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
|
||||||
* @param stream - the socket stream to lookup.
|
// Extra check, just in case
|
||||||
* @return The underlying socket address, or NULL if not found.
|
if (stream instanceof FilterInputStream)
|
||||||
* @throws IllegalAccessException Unable to access socket field.
|
return waitSocketAddress(getInputStream((FilterInputStream) stream));
|
||||||
*/
|
|
||||||
private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException {
|
SocketAddress result = inputLookup.get(stream);
|
||||||
// Extra check, just in case
|
|
||||||
if (stream instanceof FilterInputStream)
|
if (result == null) {
|
||||||
return waitSocketAddress(getInputStream((FilterInputStream) stream));
|
Socket socket = lookupSocket(stream);
|
||||||
|
|
||||||
SocketAddress result = inputLookup.get(stream);
|
// Save it
|
||||||
|
result = socket.getRemoteSocketAddress();
|
||||||
if (result == null) {
|
inputLookup.put(stream, result);
|
||||||
Socket socket = lookupSocket(stream);
|
}
|
||||||
|
return result;
|
||||||
// Save it
|
}
|
||||||
result = socket.getRemoteSocketAddress();
|
|
||||||
inputLookup.put(stream, result);
|
/**
|
||||||
}
|
* Retrieve the underlying input stream that is associated with a given filter input stream.
|
||||||
return result;
|
* @param filtered - the filter input stream.
|
||||||
}
|
* @return The underlying input stream that is being filtered.
|
||||||
|
* @throws FieldAccessException Unable to access input stream.
|
||||||
/**
|
*/
|
||||||
* Retrieve the underlying input stream that is associated with a given filter input stream.
|
protected static InputStream getInputStream(FilterInputStream filtered) {
|
||||||
* @param filtered - the filter input stream.
|
if (filteredInputField == null)
|
||||||
* @return The underlying input stream that is being filtered.
|
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
|
||||||
* @throws FieldAccessException Unable to access input stream.
|
getFieldByType("in", InputStream.class);
|
||||||
*/
|
|
||||||
protected static InputStream getInputStream(FilterInputStream filtered) {
|
InputStream current = filtered;
|
||||||
if (filteredInputField == null)
|
|
||||||
filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true).
|
try {
|
||||||
getFieldByType("in", InputStream.class);
|
// Iterate until we find the real input stream
|
||||||
|
while (current instanceof FilterInputStream) {
|
||||||
InputStream current = filtered;
|
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
|
||||||
|
}
|
||||||
try {
|
return current;
|
||||||
// Iterate until we find the real input stream
|
} catch (IllegalAccessException e) {
|
||||||
while (current instanceof FilterInputStream) {
|
throw new FieldAccessException("Cannot access filtered input field.", e);
|
||||||
current = (InputStream) FieldUtils.readField(filteredInputField, current, true);
|
}
|
||||||
}
|
}
|
||||||
return current;
|
|
||||||
} catch (IllegalAccessException e) {
|
@Override
|
||||||
throw new FieldAccessException("Cannot access filtered input field.", e);
|
public void setSocketInjector(SocketAddress address, SocketInjector injector) {
|
||||||
}
|
if (address == null)
|
||||||
}
|
throw new IllegalArgumentException("address cannot be NULL");
|
||||||
|
if (injector == null)
|
||||||
@Override
|
throw new IllegalArgumentException("injector cannot be NULL.");
|
||||||
public void setSocketInjector(SocketAddress address, SocketInjector injector) {
|
|
||||||
if (address == null)
|
SocketInjector previous = addressLookup.put(address, injector);
|
||||||
throw new IllegalArgumentException("address cannot be NULL");
|
|
||||||
if (injector == null)
|
// Any previous temporary players will also be associated
|
||||||
throw new IllegalArgumentException("injector cannot be NULL.");
|
if (previous != null) {
|
||||||
|
// Update the reference to any previous injector
|
||||||
SocketInjector previous = addressLookup.put(address, injector);
|
onPreviousSocketOverwritten(previous, injector);
|
||||||
|
}
|
||||||
// Any previous temporary players will also be associated
|
}
|
||||||
if (previous != null) {
|
|
||||||
// Update the reference to any previous injector
|
@Override
|
||||||
onPreviousSocketOverwritten(previous, injector);
|
public void cleanupAll() {
|
||||||
}
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void cleanupAll() {
|
* Lookup the underlying socket of a stream through reflection.
|
||||||
// Do nothing
|
* @param stream - the socket stream.
|
||||||
}
|
* @return The underlying socket.
|
||||||
|
* @throws IllegalAccessException If reflection failed.
|
||||||
/**
|
*/
|
||||||
* Lookup the underlying socket of a stream through reflection.
|
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
|
||||||
* @param stream - the socket stream.
|
if (stream instanceof FilterInputStream) {
|
||||||
* @return The underlying socket.
|
return lookupSocket(getInputStream((FilterInputStream) stream));
|
||||||
* @throws IllegalAccessException If reflection failed.
|
} else {
|
||||||
*/
|
// Just do it
|
||||||
private static Socket lookupSocket(InputStream stream) throws IllegalAccessException {
|
Field socketField = FuzzyReflection.fromObject(stream, true).
|
||||||
if (stream instanceof FilterInputStream) {
|
getFieldByType("socket", Socket.class);
|
||||||
return lookupSocket(getInputStream((FilterInputStream) stream));
|
|
||||||
} else {
|
return (Socket) FieldUtils.readField(socketField, stream, true);
|
||||||
// Just do it
|
}
|
||||||
Field socketField = FuzzyReflection.fromObject(stream, true).
|
}
|
||||||
getFieldByType("socket", Socket.class);
|
}
|
||||||
|
|
||||||
return (Socket) FieldUtils.readField(socketField, stream, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single send packet command.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
class QueuedSendPacket {
|
||||||
|
private final Object packet;
|
||||||
|
private final boolean filtered;
|
||||||
|
|
||||||
|
public QueuedSendPacket(Object packet, boolean filtered) {
|
||||||
|
this.packet = packet;
|
||||||
|
this.filtered = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the underlying packet that will be sent.
|
||||||
|
* @return The underlying packet.
|
||||||
|
*/
|
||||||
|
public Object getPacket() {
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the packet should be intercepted by packet listeners.
|
||||||
|
* @return TRUE if it should, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isFiltered() {
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
}
|
@ -1,197 +1,197 @@
|
|||||||
/*
|
/*
|
||||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||||
* Copyright (C) 2012 Kristian S. Stangeland
|
* Copyright (C) 2012 Kristian S. Stangeland
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||||
* the License, or (at your option) any later version.
|
* the License, or (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
* See the GNU General Public License for more details.
|
* See the GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program;
|
* You should have received a copy of the GNU General Public License along with this program;
|
||||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||||
* 02111-1307 USA
|
* 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.comphenix.protocol.injector.server;
|
package com.comphenix.protocol.injector.server;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import net.sf.cglib.proxy.Callback;
|
import net.sf.cglib.proxy.Callback;
|
||||||
import net.sf.cglib.proxy.CallbackFilter;
|
import net.sf.cglib.proxy.CallbackFilter;
|
||||||
import net.sf.cglib.proxy.Enhancer;
|
import net.sf.cglib.proxy.Enhancer;
|
||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
import net.sf.cglib.proxy.MethodInterceptor;
|
||||||
import net.sf.cglib.proxy.MethodProxy;
|
import net.sf.cglib.proxy.MethodProxy;
|
||||||
import net.sf.cglib.proxy.NoOp;
|
import net.sf.cglib.proxy.NoOp;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
import org.bukkit.Server;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import com.comphenix.protocol.injector.PacketConstructor;
|
import com.comphenix.protocol.injector.PacketConstructor;
|
||||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create fake player instances that represents pre-authenticated clients.
|
* Create fake player instances that represents pre-authenticated clients.
|
||||||
*/
|
*/
|
||||||
public class TemporaryPlayerFactory {
|
public class TemporaryPlayerFactory {
|
||||||
// Helpful constructors
|
// Helpful constructors
|
||||||
private final PacketConstructor chatPacket;
|
private final PacketConstructor chatPacket;
|
||||||
|
|
||||||
// Prevent too many class creations
|
// Prevent too many class creations
|
||||||
private static CallbackFilter callbackFilter;
|
private static CallbackFilter callbackFilter;
|
||||||
|
|
||||||
public TemporaryPlayerFactory() {
|
public TemporaryPlayerFactory() {
|
||||||
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
|
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the injector from a given player if it contains one.
|
* Retrieve the injector from a given player if it contains one.
|
||||||
* @param player - the player that may contain a reference to a player injector.
|
* @param player - the player that may contain a reference to a player injector.
|
||||||
* @return The referenced player injector, or NULL if none can be found.
|
* @return The referenced player injector, or NULL if none can be found.
|
||||||
*/
|
*/
|
||||||
public static SocketInjector getInjectorFromPlayer(Player player) {
|
public static SocketInjector getInjectorFromPlayer(Player player) {
|
||||||
if (player instanceof InjectorContainer) {
|
if (player instanceof InjectorContainer) {
|
||||||
return ((InjectorContainer) player).getInjector();
|
return ((InjectorContainer) player).getInjector();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the player injector, if possible.
|
* Set the player injector, if possible.
|
||||||
* @param player - the player to update.
|
* @param player - the player to update.
|
||||||
* @param injector - the injector to store.
|
* @param injector - the injector to store.
|
||||||
*/
|
*/
|
||||||
public static void setInjectorInPlayer(Player player, SocketInjector injector) {
|
public static void setInjectorInPlayer(Player player, SocketInjector injector) {
|
||||||
((InjectorContainer) player).setInjector(injector);
|
((InjectorContainer) player).setInjector(injector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a temporary player that supports a subset of every player command.
|
* Construct a temporary player that supports a subset of every player command.
|
||||||
* <p>
|
* <p>
|
||||||
* Supported methods include:
|
* Supported methods include:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>getPlayer()</li>
|
* <li>getPlayer()</li>
|
||||||
* <li>getAddress()</li>
|
* <li>getAddress()</li>
|
||||||
* <li>getServer()</li>
|
* <li>getServer()</li>
|
||||||
* <li>chat(String)</li>
|
* <li>chat(String)</li>
|
||||||
* <li>sendMessage(String)</li>
|
* <li>sendMessage(String)</li>
|
||||||
* <li>sendMessage(String[])</li>
|
* <li>sendMessage(String[])</li>
|
||||||
* <li>kickPlayer(String)</li>
|
* <li>kickPlayer(String)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Note that a temporary player has not yet been assigned a name, and thus cannot be
|
* Note that a temporary player has not yet been assigned a name, and thus cannot be
|
||||||
* uniquely identified. Use the address instead.
|
* uniquely identified. Use the address instead.
|
||||||
* @param injector - the player injector used.
|
* @param injector - the player injector used.
|
||||||
* @param server - the current server.
|
* @param server - the current server.
|
||||||
* @return A temporary player instance.
|
* @return A temporary player instance.
|
||||||
*/
|
*/
|
||||||
public Player createTemporaryPlayer(final Server server) {
|
public Player createTemporaryPlayer(final Server server) {
|
||||||
|
|
||||||
// Default implementation
|
// Default implementation
|
||||||
Callback implementation = new MethodInterceptor() {
|
Callback implementation = new MethodInterceptor() {
|
||||||
@Override
|
@Override
|
||||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||||
|
|
||||||
String methodName = method.getName();
|
String methodName = method.getName();
|
||||||
SocketInjector injector = ((InjectorContainer) obj).getInjector();
|
SocketInjector injector = ((InjectorContainer) obj).getInjector();
|
||||||
|
|
||||||
if (injector == null)
|
if (injector == null)
|
||||||
throw new IllegalStateException("Unable to find injector.");
|
throw new IllegalStateException("Unable to find injector.");
|
||||||
|
|
||||||
// Use the socket to get the address
|
// Use the socket to get the address
|
||||||
if (methodName.equalsIgnoreCase("isOnline"))
|
if (methodName.equalsIgnoreCase("isOnline"))
|
||||||
return injector.getSocket() != null && injector.getSocket().isConnected();
|
return injector.getSocket() != null && injector.getSocket().isConnected();
|
||||||
if (methodName.equalsIgnoreCase("getName"))
|
if (methodName.equalsIgnoreCase("getName"))
|
||||||
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
|
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
|
||||||
if (methodName.equalsIgnoreCase("getPlayer"))
|
if (methodName.equalsIgnoreCase("getPlayer"))
|
||||||
return injector.getUpdatedPlayer();
|
return injector.getUpdatedPlayer();
|
||||||
if (methodName.equalsIgnoreCase("getAddress"))
|
if (methodName.equalsIgnoreCase("getAddress"))
|
||||||
return injector.getAddress();
|
return injector.getAddress();
|
||||||
if (methodName.equalsIgnoreCase("getServer"))
|
if (methodName.equalsIgnoreCase("getServer"))
|
||||||
return server;
|
return server;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Handle send message methods
|
// Handle send message methods
|
||||||
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
|
if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) {
|
||||||
Object argument = args[0];
|
Object argument = args[0];
|
||||||
|
|
||||||
// Dynamic overloading
|
// Dynamic overloading
|
||||||
if (argument instanceof String) {
|
if (argument instanceof String) {
|
||||||
return sendMessage(injector, (String) argument);
|
return sendMessage(injector, (String) argument);
|
||||||
} else if (argument instanceof String[]) {
|
} else if (argument instanceof String[]) {
|
||||||
for (String message : (String[]) argument) {
|
for (String message : (String[]) argument) {
|
||||||
sendMessage(injector, message);
|
sendMessage(injector, message);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
throw e.getCause();
|
throw e.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also, handle kicking
|
// Also, handle kicking
|
||||||
if (methodName.equalsIgnoreCase("kickPlayer")) {
|
if (methodName.equalsIgnoreCase("kickPlayer")) {
|
||||||
injector.disconnect((String) args[0]);
|
injector.disconnect((String) args[0]);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore all other methods
|
// Ignore all other methods
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"The method " + method.getName() + " is not supported for temporary players.");
|
"The method " + method.getName() + " is not supported for temporary players.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Shared callback filter
|
// Shared callback filter
|
||||||
if (callbackFilter == null) {
|
if (callbackFilter == null) {
|
||||||
callbackFilter = new CallbackFilter() {
|
callbackFilter = new CallbackFilter() {
|
||||||
@Override
|
@Override
|
||||||
public int accept(Method method) {
|
public int accept(Method method) {
|
||||||
// Do not override the object method or the superclass methods
|
// Do not override the object method or the superclass methods
|
||||||
if (method.getDeclaringClass().equals(Object.class) ||
|
if (method.getDeclaringClass().equals(Object.class) ||
|
||||||
method.getDeclaringClass().equals(InjectorContainer.class))
|
method.getDeclaringClass().equals(InjectorContainer.class))
|
||||||
return 0;
|
return 0;
|
||||||
else
|
else
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// CGLib is amazing
|
// CGLib is amazing
|
||||||
Enhancer ex = new Enhancer();
|
Enhancer ex = new Enhancer();
|
||||||
ex.setSuperclass(InjectorContainer.class);
|
ex.setSuperclass(InjectorContainer.class);
|
||||||
ex.setInterfaces(new Class[] { Player.class });
|
ex.setInterfaces(new Class[] { Player.class });
|
||||||
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
|
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
|
||||||
ex.setCallbackFilter(callbackFilter);
|
ex.setCallbackFilter(callbackFilter);
|
||||||
|
|
||||||
return (Player) ex.create();
|
return (Player) ex.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a temporary player with the given associated socket injector.
|
* Construct a temporary player with the given associated socket injector.
|
||||||
* @param server - the parent server.
|
* @param server - the parent server.
|
||||||
* @param injector - the referenced socket injector.
|
* @param injector - the referenced socket injector.
|
||||||
* @return The temporary player.
|
* @return The temporary player.
|
||||||
*/
|
*/
|
||||||
public Player createTemporaryPlayer(Server server, SocketInjector injector) {
|
public Player createTemporaryPlayer(Server server, SocketInjector injector) {
|
||||||
Player temporary = createTemporaryPlayer(server);
|
Player temporary = createTemporaryPlayer(server);
|
||||||
|
|
||||||
((InjectorContainer) temporary).setInjector(injector);
|
((InjectorContainer) temporary).setInjector(injector);
|
||||||
return temporary;
|
return temporary;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a message to the given client.
|
* Send a message to the given client.
|
||||||
* @param injector - the injector representing the client.
|
* @param injector - the injector representing the client.
|
||||||
* @param message - a message.
|
* @param message - a message.
|
||||||
* @return Always NULL.
|
* @return Always NULL.
|
||||||
* @throws InvocationTargetException If the message couldn't be sent.
|
* @throws InvocationTargetException If the message couldn't be sent.
|
||||||
* @throws FieldAccessException If we were unable to construct the message packet.
|
* @throws FieldAccessException If we were unable to construct the message packet.
|
||||||
*/
|
*/
|
||||||
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
|
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
|
||||||
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
|
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,12 +114,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
|
|||||||
public void checkListener(Set<PacketListener> listeners) {
|
public void checkListener(Set<PacketListener> listeners) {
|
||||||
// Yes, really
|
// Yes, really
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postWorldLoaded() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePlayer(Player player) {
|
public void updatePlayer(Player player) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
Loading…
Reference in New Issue
Block a user