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:
Kristian S. Stangeland 2013-07-06 00:24:11 +02:00
parent 81e158d74a
commit 5e35f46b96
18 changed files with 1921 additions and 1375 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, InternalManager {
public final class PacketFilterManager implements ProtocolManager, ListenerInvoker {
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,68 +220,97 @@ 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
@ -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)

View File

@ -18,6 +18,7 @@
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.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -41,7 +42,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
* *
* @author Kristian * @author Kristian
*/ */
class InjectedServerConnection { public class InjectedServerConnection {
// A number of things can go wrong ... // A number of things can go wrong ...
public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit."); 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_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen.");
@ -66,12 +67,21 @@ class InjectedServerConnection {
private List<VolatileField> listFields; private List<VolatileField> listFields;
private List<ReplacedArrayList<Object>> replacedLists; private List<ReplacedArrayList<Object>> replacedLists;
// The current detected server socket
public enum ServerSocketType {
SERVER_CONNECTION,
LISTENER_THREAD,
}
// Used to inject net handlers // Used to inject net handlers
private NetLoginInjector netLoginInjector; private NetLoginInjector netLoginInjector;
// Inject server connections // Inject server connections
private AbstractInputStreamLookup socketInjector; private AbstractInputStreamLookup socketInjector;
// Detected by the initializer
private ServerSocketType socketType;
private Server server; private Server server;
private ErrorReporter reporter; private ErrorReporter reporter;
private boolean hasAttempted; private boolean hasAttempted;
@ -88,7 +98,10 @@ class InjectedServerConnection {
this.netLoginInjector = netLoginInjector; this.netLoginInjector = netLoginInjector;
} }
public void injectList() { /**
* Initial reflective detective work. Will be automatically called by most methods in this class.
*/
public void initialize() {
// Only execute this method once // Only execute this method once
if (!hasAttempted) if (!hasAttempted)
hasAttempted = true; hasAttempted = true;
@ -112,12 +125,11 @@ class InjectedServerConnection {
getMethodByParameters("getServerConnection", getMethodByParameters("getServerConnection",
MinecraftReflection.getServerConnectionClass(), new Class[] {}); MinecraftReflection.getServerConnectionClass(), new Class[] {});
// We're using Minecraft 1.3.1 // We're using Minecraft 1.3.1
injectServerConnection(); socketType = ServerSocketType.SERVER_CONNECTION;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Minecraft 1.2.5 or lower // Minecraft 1.2.5 or lower
injectListenerThread(); socketType = ServerSocketType.LISTENER_THREAD;
} catch (Exception e) { } catch (Exception e) {
// Oh damn - inform the player // Oh damn - inform the player
@ -125,11 +137,77 @@ class InjectedServerConnection {
} }
} }
/**
* Retrieve the known server socket type.
* <p>
* This depends on the version of CraftBukkit we are using.
* @return The server socket type.
*/
public ServerSocketType getServerSocketType() {
return socketType;
}
/**
* Inject the connection interceptor into the correct server socket implementation.
*/
public void injectList() {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION) {
injectServerConnection();
} else if (socketType == ServerSocketType.LISTENER_THREAD) {
injectListenerThread();
} else {
// Damn it
throw new IllegalStateException("Unable to detected server connection.");
}
}
/**
* Retrieve the listener thread field.
*/
private void initializeListenerField() {
if (listenerThreadField == null)
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
}
/**
* Retrieve the listener thread object, or NULL the server isn't using this socket implementation.
* @return The listener thread, or NULL.
* @throws IllegalAccessException Cannot access field.
* @hrows RuntimeException Unexpected class structure - the field doesn't exist.
*/
public Object getListenerThread() throws RuntimeException, IllegalAccessException {
initialize();
if (socketType == ServerSocketType.LISTENER_THREAD) {
initializeListenerField();
return listenerThreadField.get(minecraftServer);
} else {
return null;
}
}
/**
* Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation.
* @return The socket connection, or NULL.
* @throws IllegalAccessException If the reflective operation failed.
* @throws IllegalArgumentException If the reflective operation failed.
* @throws InvocationTargetException If the reflective operation failed.
*/
public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
initialize();
if (socketType == ServerSocketType.SERVER_CONNECTION)
return serverConnectionMethod.invoke(minecraftServer);
else
return null;
}
private void injectListenerThread() { private void injectListenerThread() {
try { try {
if (listenerThreadField == null) initializeListenerField();
listenerThreadField = FuzzyReflection.fromObject(minecraftServer).
getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass());
} catch (RuntimeException e) { } catch (RuntimeException e) {
reporter.reportDetailed(this, reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e) Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e)
@ -141,7 +219,7 @@ class InjectedServerConnection {
// Attempt to get the thread // Attempt to get the thread
try { try {
listenerThread = listenerThreadField.get(minecraftServer); listenerThread = getListenerThread();
} catch (Exception e) { } catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e)); reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e));
return; return;
@ -160,7 +238,7 @@ class InjectedServerConnection {
// Careful - we might fail // Careful - we might fail
try { try {
serverConnection = serverConnectionMethod.invoke(minecraftServer); serverConnection = getServerConnection();
} catch (Exception e) { } catch (Exception e) {
reporter.reportDetailed(this, reporter.reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e) Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e)

View File

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

View File

@ -145,14 +145,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
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.
* @return Injection method for reading server packets. * @return Injection method for reading server packets.

View File

@ -26,11 +26,6 @@ public abstract class AbstractInputStreamLookup {
*/ */
public abstract void inject(Object container); public abstract void inject(Object container);
/**
* Invoked when the world has loaded.
*/
public abstract void postWorldLoaded();
/** /**
* Retrieve the associated socket injector for a player. * Retrieve the associated socket injector for a player.
* @param input - the indentifying filtered input stream. * @param input - the indentifying filtered input stream.

View File

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

View File

@ -53,11 +53,6 @@ class InputStreamReflectLookup extends AbstractInputStreamLookup {
// Do nothing // Do nothing
} }
@Override
public void postWorldLoaded() {
// Nothing again
}
@Override @Override
public SocketInjector peekSocketInjector(SocketAddress address) { public SocketInjector peekSocketInjector(SocketAddress address) {
try { try {

View File

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

View File

@ -115,11 +115,6 @@ class DummyPlayerHandler implements PlayerInjectionHandler {
// 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