ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java

1176 lines
37 KiB
Java
Raw Normal View History

2012-10-15 00:31:55 +02:00
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* 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
2012-10-15 00:31:55 +02:00
* 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;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2012-10-15 00:31:55 +02:00
* 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;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
2012-10-15 00:31:55 +02:00
* 02111-1307 USA
*/
package com.comphenix.protocol.injector;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
2012-10-15 00:31:55 +02:00
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Sender;
2012-10-15 00:31:55 +02:00
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.async.AsyncMarker;
2012-10-29 04:17:53 +01:00
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.*;
2016-03-19 21:01:38 +01:00
import com.comphenix.protocol.injector.netty.ProtocolInjector;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.InterceptWritePacket;
import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
import com.comphenix.protocol.injector.packet.PacketRegistry;
2012-10-15 00:31:55 +02:00
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
2013-10-28 19:09:11 +01:00
import com.comphenix.protocol.injector.player.PlayerInjector.ServerHandlerNull;
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
import com.comphenix.protocol.injector.spigot.SpigotPacketInjector;
2012-10-15 00:31:55 +02:00
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Util;
2012-10-15 00:31:55 +02:00
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
2012-10-15 00:31:55 +02:00
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
2012-10-15 00:31:55 +02:00
import io.netty.channel.Channel;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
public final class PacketFilterManager implements 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_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector");
public static final ReportType REPORT_PLUGIN_DEPEND_MISSING =
new ReportType("%s doesn't depend on ProtocolLib. Check that its plugin.yml has a 'depend' directive.");
// Registering packet IDs that are not supported
public static final ReportType REPORT_UNSUPPORTED_SERVER_PACKET = new ReportType("[%s] Unsupported server packet in current Minecraft version: %s");
public static final ReportType REPORT_UNSUPPORTED_CLIENT_PACKET = new ReportType("[%s] Unsupported client packet in current Minecraft version: %s");
// Problems injecting and uninjecting players
public static final ReportType REPORT_CANNOT_UNINJECT_PLAYER = new ReportType("Unable to uninject net handler for player.");
public static final ReportType REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER = new ReportType("Unable to uninject logged off player.");
public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player.");
2012-10-15 00:31:55 +02:00
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");
/**
* The number of ticks in a second.
*/
public static final int TICKS_PER_SECOND = 20;
// The amount of time to wait until we actually unhook every player
private static final int UNHOOK_DELAY = 5 * TICKS_PER_SECOND;
// Delayed unhook
private DelayedSingleTask unhookTask;
2012-10-15 00:31:55 +02:00
// Create a concurrent set
private Set<PacketListener> packetListeners =
2012-10-15 00:31:55 +02:00
Collections.newSetFromMap(new ConcurrentHashMap<PacketListener, Boolean>());
2012-10-15 00:31:55 +02:00
// Packet injection
private PacketInjector packetInjector;
// Different injection types per game phase
2012-10-15 00:31:55 +02:00
private PlayerInjectionHandler playerInjection;
// Intercepting write packet methods
private InterceptWritePacket interceptWritePacket;
// Whether or not a packet must be input buffered
private volatile Set<PacketType> inputBufferedPackets = Sets.newHashSet();
2012-10-15 00:31:55 +02:00
// The two listener containers
2012-11-04 01:10:05 +01:00
private SortedPacketListenerList recievedListeners;
private SortedPacketListenerList sendingListeners;
2012-10-15 00:31:55 +02:00
// Whether or not this class has been closed
private volatile boolean hasClosed;
2012-10-15 00:31:55 +02:00
// The default class loader
private ClassLoader classLoader;
2012-10-29 04:17:53 +01:00
// Error repoter
private ErrorReporter reporter;
// The current server
private Server server;
// The current ProtocolLib library
private Plugin library;
2012-10-15 00:31:55 +02:00
// The async packet handler
private AsyncFilterManager asyncFilterManager;
2012-10-15 00:31:55 +02:00
// Valid server and client packets
private boolean knowsServerPackets;
private boolean knowsClientPackets;
// Ensure that we're not performing too may injections
private AtomicInteger phaseLoginCount = new AtomicInteger(0);
private AtomicInteger phasePlayingCount = new AtomicInteger(0);
// Whether or not plugins are using the send/receive methods
private AtomicBoolean packetCreation = new AtomicBoolean();
// Spigot listener, if in use
private SpigotPacketInjector spigotInjector;
// Netty injector (for 1.7.2)
private ProtocolInjector nettyInjector;
// Plugin verifier
private PluginVerifier pluginVerifier;
// Whether or not Location.distance(Location) exists - we assume this is the case
private boolean hasRecycleDistance = true;
// The current Minecraft version
private MinecraftVersion minecraftVersion;
// Login packets
private LoginPackets loginPackets;
2013-12-29 11:58:54 +01:00
// Debug mode
private boolean debug;
2012-10-15 00:31:55 +02:00
/**
2015-06-17 20:25:39 +02:00
* Only create instances of this class if ProtocolLib is disabled.
* @param builder - PacketFilterBuilder
2012-10-15 00:31:55 +02:00
*/
public PacketFilterManager(PacketFilterBuilder builder) {
// Used to determine if injection is needed
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;
}
};
2012-11-04 01:10:05 +01:00
// Listener containers
this.recievedListeners = new SortedPacketListenerList();
this.sendingListeners = new SortedPacketListenerList();
// References
this.unhookTask = builder.getUnhookTask();
this.server = builder.getServer();
this.classLoader = builder.getClassLoader();
this.reporter = builder.getReporter();
// The plugin verifier - we don't want to stop ProtocolLib just because its failing
try {
this.pluginVerifier = new PluginVerifier(builder.getLibrary());
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR).
messageParam(e.getMessage()).error(e));
}
// Prepare version
this.minecraftVersion = builder.getMinecraftVersion();
this.loginPackets = new LoginPackets(minecraftVersion);
// The write packet interceptor
this.interceptWritePacket = new InterceptWritePacket(reporter);
// Use the correct injection type
if (MinecraftReflection.isUsingNetty()) {
2016-03-19 21:01:38 +01:00
this.nettyInjector = new ProtocolInjector(builder.getLibrary(), this, reporter);
this.playerInjection = nettyInjector.getPlayerInjector();
this.packetInjector = nettyInjector.getPacketInjector();
} else if (builder.isNettyEnabled()) {
this.spigotInjector = new SpigotPacketInjector(reporter, this, server);
this.playerInjection = spigotInjector.getPlayerHandler();
this.packetInjector = spigotInjector.getPacketInjector();
// Set real injector, in case we need it
spigotInjector.setProxyPacketInjector(PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
playerInjection(playerInjection).
buildInjector()
);
} else {
// Initialize standard injection mangers
this.playerInjection = PlayerInjectorBuilder.newBuilder().
invoker(this).
server(server).
reporter(reporter).
packetListeners(packetListeners).
injectionFilter(isInjectionNecessary).
version(builder.getMinecraftVersion()).
buildHandler();
this.packetInjector = PacketInjectorBuilder.newBuilder().
invoker(this).
reporter(reporter).
playerInjection(playerInjection).
buildInjector();
}
this.asyncFilterManager = builder.getAsyncManager();
this.library = builder.getLibrary();
// Attempt to load the list of server and client packets
2012-10-15 00:31:55 +02:00
try {
knowsServerPackets = PacketRegistry.getClientPacketTypes() != null;
knowsClientPackets = PacketRegistry.getServerPacketTypes() != null;
} catch (FieldAccessException e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e));
2012-10-15 00:31:55 +02:00
}
}
/**
* Construct a new packet filter builder.
* @return New builder.
*/
public static PacketFilterBuilder newBuilder() {
return new PacketFilterBuilder();
Experimental: InputStream -> Socket lookup by intercepting accept(). Previously, we have used a BlockingHashMap to simply lock the packet read thread until we have had a chance to intercept the NetLoginHandler/PendingConnection and store InputStream -> PlayerInjector -> TemporaryPlayer. Problem is, this could potentially cause problems if, for some reason, a packet is intercepted after the player has logged out and the player injector has been removed from the lookup map. In that case, the read thread would wait until it reaches the default timeout of 2 seconds. Locking threads is fairly inefficient in general, and waiting for the server connection thread to update the NetLoginHandler list could take a while. Instead, ProtocolLib will now intercept any Socket accepted in the server's main ServerSocket, and record any calls to getInputStream(). That way, we can get a InputStream -> Socket mapping before the server thread ever creates the read and write threads in NetLoginHandler -> NetworkManager. Unfortunately, it's not trivial to swap out the ServerSocket in the DedicatedServerConnectionThread - we actually have to trigger the accept() thread and move through a cycle of the loop before our custom ServerSocket is used. To do this, we will actually connect to the server and read its MOTD manually, hopefully getting to it before any other players. This creates a slight overhead of a couple of threads per server start, but it's probably much better than locking the read thread. More testing is needed though before this can be confirmed.
2013-02-25 01:59:48 +01:00
}
@Override
public int getProtocolVersion(Player player) {
return playerInjection.getProtocolVersion(player);
}
@Override
public MinecraftVersion getMinecraftVersion() {
return minecraftVersion;
}
2012-10-15 00:31:55 +02:00
@Override
public AsynchronousManager getAsynchronousManager() {
return asyncFilterManager;
}
2013-12-29 11:58:54 +01:00
@Override
public boolean isDebug() {
return debug;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
2013-12-29 11:58:54 +01:00
// Inform components that can handle debug mode
if (nettyInjector != null) {
nettyInjector.setDebug(debug);
}
}
2012-10-15 00:31:55 +02:00
/**
* Retrieves how the server packets are read.
* @return Injection method for reading server packets.
*/
@Override
2012-10-15 00:31:55 +02:00
public PlayerInjectHooks getPlayerHook() {
return playerInjection.getPlayerHook();
}
/**
* Sets how the server packets are read.
* @param playerHook - the new injection method for reading server packets.
*/
@Override
2012-10-15 00:31:55 +02:00
public void setPlayerHook(PlayerInjectHooks playerHook) {
playerInjection.setPlayerHook(playerHook);
}
@Override
public ImmutableSet<PacketListener> getPacketListeners() {
return ImmutableSet.copyOf(packetListeners);
}
@Override
public InterceptWritePacket getInterceptWritePacket() {
return interceptWritePacket;
}
/**
* Warn of common programming mistakes.
* @param plugin - plugin to check.
*/
private void printPluginWarnings(Plugin plugin) {
if (pluginVerifier == null)
return;
try {
switch (pluginVerifier.verify(plugin)) {
case NO_DEPEND:
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName()));
case VALID:
// Do nothing
break;
}
} catch (Exception e) {
reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR).messageParam(e.getMessage()));
}
}
2012-10-15 00:31:55 +02:00
@Override
public void addPacketListener(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL.");
2012-10-15 00:31:55 +02:00
// A listener can only be added once
if (packetListeners.contains(listener))
return;
2012-10-15 00:31:55 +02:00
ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist();
boolean hasSending = sending != null && sending.isEnabled();
boolean hasReceiving = receiving != null && receiving.isEnabled();
2014-09-07 21:27:46 +02:00
// Check plugin
if (!(hasSending && sending.getOptions().contains(ListenerOptions.SKIP_PLUGIN_VERIFIER)) &&
!(hasReceiving && receiving.getOptions().contains(ListenerOptions.SKIP_PLUGIN_VERIFIER))) {
2014-09-07 21:27:46 +02:00
printPluginWarnings(listener.getPlugin());
}
2012-10-15 00:31:55 +02:00
if (hasSending || hasReceiving) {
// Add listeners and hooks
if (hasSending) {
// This doesn't make any sense
if (sending.getOptions().contains(ListenerOptions.INTERCEPT_INPUT_BUFFER)) {
throw new IllegalArgumentException("Sending whitelist cannot require input bufferes to be intercepted.");
}
2012-10-15 00:31:55 +02:00
verifyWhitelist(listener, sending);
sendingListeners.addListener(listener, sending);
enablePacketFilters(listener, sending.getTypes());
2012-10-15 00:31:55 +02:00
// Make sure this is possible
playerInjection.checkListener(listener);
}
Experimental: InputStream -> Socket lookup by intercepting accept(). Previously, we have used a BlockingHashMap to simply lock the packet read thread until we have had a chance to intercept the NetLoginHandler/PendingConnection and store InputStream -> PlayerInjector -> TemporaryPlayer. Problem is, this could potentially cause problems if, for some reason, a packet is intercepted after the player has logged out and the player injector has been removed from the lookup map. In that case, the read thread would wait until it reaches the default timeout of 2 seconds. Locking threads is fairly inefficient in general, and waiting for the server connection thread to update the NetLoginHandler list could take a while. Instead, ProtocolLib will now intercept any Socket accepted in the server's main ServerSocket, and record any calls to getInputStream(). That way, we can get a InputStream -> Socket mapping before the server thread ever creates the read and write threads in NetLoginHandler -> NetworkManager. Unfortunately, it's not trivial to swap out the ServerSocket in the DedicatedServerConnectionThread - we actually have to trigger the accept() thread and move through a cycle of the loop before our custom ServerSocket is used. To do this, we will actually connect to the server and read its MOTD manually, hopefully getting to it before any other players. This creates a slight overhead of a couple of threads per server start, but it's probably much better than locking the read thread. More testing is needed though before this can be confirmed.
2013-02-25 01:59:48 +01:00
if (hasSending)
incrementPhases(processPhase(sending));
Experimental: InputStream -> Socket lookup by intercepting accept(). Previously, we have used a BlockingHashMap to simply lock the packet read thread until we have had a chance to intercept the NetLoginHandler/PendingConnection and store InputStream -> PlayerInjector -> TemporaryPlayer. Problem is, this could potentially cause problems if, for some reason, a packet is intercepted after the player has logged out and the player injector has been removed from the lookup map. In that case, the read thread would wait until it reaches the default timeout of 2 seconds. Locking threads is fairly inefficient in general, and waiting for the server connection thread to update the NetLoginHandler list could take a while. Instead, ProtocolLib will now intercept any Socket accepted in the server's main ServerSocket, and record any calls to getInputStream(). That way, we can get a InputStream -> Socket mapping before the server thread ever creates the read and write threads in NetLoginHandler -> NetworkManager. Unfortunately, it's not trivial to swap out the ServerSocket in the DedicatedServerConnectionThread - we actually have to trigger the accept() thread and move through a cycle of the loop before our custom ServerSocket is used. To do this, we will actually connect to the server and read its MOTD manually, hopefully getting to it before any other players. This creates a slight overhead of a couple of threads per server start, but it's probably much better than locking the read thread. More testing is needed though before this can be confirmed.
2013-02-25 01:59:48 +01:00
// Handle receivers after senders
2012-10-15 00:31:55 +02:00
if (hasReceiving) {
verifyWhitelist(listener, receiving);
recievedListeners.addListener(listener, receiving);
enablePacketFilters(listener, receiving.getTypes());
2012-10-15 00:31:55 +02:00
}
if (hasReceiving)
incrementPhases(processPhase(receiving));
2012-10-15 00:31:55 +02:00
// Inform our injected hooks
packetListeners.add(listener);
updateRequireInputBuffers();
2012-10-15 00:31:55 +02:00
}
}
private GamePhase processPhase(ListeningWhitelist whitelist) {
// Determine if this is a login packet, ensuring that gamephase detection is enabled
if (!whitelist.getGamePhase().hasLogin() &&
!whitelist.getOptions().contains(ListenerOptions.DISABLE_GAMEPHASE_DETECTION)) {
for (PacketType type : whitelist.getTypes()) {
if (loginPackets.isLoginPacket(type)) {
return GamePhase.BOTH;
}
}
}
return whitelist.getGamePhase();
}
/**
* Invoked when we need to update the input buffer set.
*/
private void updateRequireInputBuffers() {
Set<PacketType> updated = Sets.newHashSet();
for (PacketListener listener : packetListeners) {
ListeningWhitelist whitelist = listener.getReceivingWhitelist();
// We only check the recieving whitelist
if (whitelist.getOptions().contains(ListenerOptions.INTERCEPT_INPUT_BUFFER)) {
updated.addAll(whitelist.getTypes());
}
}
// Update it
this.inputBufferedPackets = updated;
this.packetInjector.inputBuffersChanged(updated);
}
/**
* Invoked to handle the different game phases of a added listener.
* @param phase - listener's game game phase.
*/
private void incrementPhases(GamePhase phase) {
if (phase.hasLogin())
phaseLoginCount.incrementAndGet();
// We may have to inject into every current player
if (phase.hasPlaying()) {
if (phasePlayingCount.incrementAndGet() == 1) {
// If we're about to uninitialize every player, cancel that instead
if (unhookTask.isRunning())
unhookTask.cancel();
else
// Inject our hook into already existing players
2015-02-12 23:02:38 +01:00
initializePlayers(Util.getOnlinePlayers());
}
}
}
/**
* Invoked to handle the different game phases of a removed listener.
* @param phase - listener's game game phase.
*/
private void decrementPhases(GamePhase phase) {
if (phase.hasLogin())
phaseLoginCount.decrementAndGet();
// We may have to inject into every current player
if (phase.hasPlaying()) {
if (phasePlayingCount.decrementAndGet() == 0) {
// Schedule unhooking in the future
unhookTask.schedule(UNHOOK_DELAY, new Runnable() {
@Override
public void run() {
// Inject our hook into already existing players
2015-02-12 23:02:38 +01:00
uninitializePlayers(Util.getOnlinePlayers());
}
});
}
}
}
2012-10-15 00:31:55 +02:00
/**
* Determine if the packet IDs in a whitelist is valid.
* @param listener - the listener that will be mentioned in the error.
* @param whitelist - whitelist of packet IDs.
* @throws IllegalArgumentException If the whitelist is illegal.
*/
2016-03-19 21:01:38 +01:00
@Override
public void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist) {
for (PacketType type : whitelist.getTypes()) {
if (type == null) {
throw new IllegalArgumentException(String.format("Packet type in in listener %s was NULL.",
PacketAdapter.getPluginName(listener))
2012-10-15 00:31:55 +02:00
);
}
}
}
2012-10-15 00:31:55 +02:00
@Override
public void removePacketListener(PacketListener listener) {
if (listener == null)
throw new IllegalArgumentException("listener cannot be NULL");
List<PacketType> sendingRemoved = null;
List<PacketType> receivingRemoved = null;
2012-10-15 00:31:55 +02:00
ListeningWhitelist sending = listener.getSendingWhitelist();
ListeningWhitelist receiving = listener.getReceivingWhitelist();
2012-10-15 00:31:55 +02:00
// Remove from the overal list of listeners
if (!packetListeners.remove(listener))
return;
// Remove listeners and phases
if (sending != null && sending.isEnabled()) {
2012-10-15 00:31:55 +02:00
sendingRemoved = sendingListeners.removeListener(listener, sending);
decrementPhases(processPhase(sending));
}
if (receiving != null && receiving.isEnabled()) {
2012-10-15 00:31:55 +02:00
receivingRemoved = recievedListeners.removeListener(listener, receiving);
decrementPhases(processPhase(receiving));
}
2012-10-15 00:31:55 +02:00
// Remove hooks, if needed
if (sendingRemoved != null && sendingRemoved.size() > 0)
disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
if (receivingRemoved != null && receivingRemoved.size() > 0)
disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
updateRequireInputBuffers();
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
public void removePacketListeners(Plugin plugin) {
// Iterate through every packet listener
for (PacketListener listener : packetListeners) {
2012-10-15 00:31:55 +02:00
// Remove the listener
if (Objects.equal(listener.getPlugin(), plugin)) {
removePacketListener(listener);
}
}
2012-10-15 00:31:55 +02:00
// Do the same for the asynchronous events
asyncFilterManager.unregisterAsyncHandlers(plugin);
}
2012-10-15 00:31:55 +02:00
@Override
public void invokePacketRecieving(PacketEvent event) {
2012-11-04 01:10:05 +01:00
if (!hasClosed) {
handlePacket(recievedListeners, event, false);
}
2012-10-15 00:31:55 +02:00
}
@Override
public void invokePacketSending(PacketEvent event) {
2012-11-04 01:10:05 +01:00
if (!hasClosed) {
handlePacket(sendingListeners, event, true);
}
2012-10-15 00:31:55 +02:00
}
@Override
public boolean requireInputBuffer(int packetId) {
return inputBufferedPackets.contains(PacketType.findLegacy(packetId, Sender.CLIENT));
}
2012-10-15 00:31:55 +02:00
/**
* Handle a packet sending or receiving event.
* <p>
* Note that we also handle asynchronous events.
* @param packetListeners - packet listeners that will receive this event.
* @param event - the evnet to broadcast.
*/
private void handlePacket(SortedPacketListenerList packetListeners, PacketEvent event, boolean sending) {
// By default, asynchronous packets are queued for processing
if (asyncFilterManager.hasAsynchronousListeners(event)) {
event.setAsyncMarker(asyncFilterManager.createAsyncMarker());
}
2012-10-15 00:31:55 +02:00
// Process synchronous events
if (sending)
2012-10-29 04:17:53 +01:00
packetListeners.invokePacketSending(reporter, event);
2012-10-15 00:31:55 +02:00
else
2012-10-29 04:17:53 +01:00
packetListeners.invokePacketRecieving(reporter, event);
2012-10-15 00:31:55 +02:00
// To cancel asynchronous processing, use the async marker
if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) {
asyncFilterManager.enqueueSyncPacket(event, event.getAsyncMarker());
// The above makes a copy of the event, so it's safe to cancel it
event.setReadOnly(false);
2012-10-15 00:31:55 +02:00
event.setCancelled(true);
}
}
// NULL marker mean we're dealing with no asynchronous listeners
2012-10-15 00:31:55 +02:00
private boolean hasAsyncCancelled(AsyncMarker marker) {
return marker == null || marker.isAsyncCancelled();
}
2012-10-15 00:31:55 +02:00
/**
* Enables packet events for a given packet ID.
* <p>
* Note that all packets are disabled by default.
*
2012-10-15 00:31:55 +02:00
* @param listener - the listener that requested to enable these filters.
* @param packets - the packet id(s).
*/
private void enablePacketFilters(PacketListener listener, Iterable<PacketType> packets) {
2012-10-15 00:31:55 +02:00
// Note the difference between unsupported and valid.
// Every packet ID between and including 0 - 255 is valid, but only a subset is supported.
for (PacketType type : packets) {
2012-10-15 00:31:55 +02:00
// Only register server packets that are actually supported by Minecraft
if (type.getSender() == Sender.SERVER) {
// Note that we may update the packet list here
if (!knowsServerPackets || PacketRegistry.getServerPacketTypes().contains(type))
playerInjection.addPacketHandler(type, listener.getSendingWhitelist().getOptions());
2012-10-15 00:31:55 +02:00
else
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET).messageParam(PacketAdapter.getPluginName(listener), type)
);
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
// As above, only for client packets
if (type.getSender() == Sender.CLIENT && packetInjector != null) {
if (!knowsClientPackets || PacketRegistry.getClientPacketTypes().contains(type))
packetInjector.addPacketHandler(type, listener.getReceivingWhitelist().getOptions());
2012-10-15 00:31:55 +02:00
else
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET).messageParam(PacketAdapter.getPluginName(listener), type)
);
2012-10-15 00:31:55 +02:00
}
}
}
/**
* Disables packet events from a given packet ID.
* @param packets - the packet id(s).
* @param side - which side the event no longer should arrive from.
*/
private void disablePacketFilters(ConnectionSide side, Iterable<PacketType> packets) {
2012-10-15 00:31:55 +02:00
if (side == null)
throw new IllegalArgumentException("side cannot be NULL.");
for (PacketType type : packets) {
2012-10-15 00:31:55 +02:00
if (side.isForServer())
playerInjection.removePacketHandler(type);
if (side.isForClient() && packetInjector != null)
packetInjector.removePacketHandler(type);
2012-10-15 00:31:55 +02:00
}
}
@Override
public void broadcastServerPacket(PacketContainer packet) {
Preconditions.checkNotNull(packet, "packet cannot be NULL.");
2015-02-12 23:02:38 +01:00
broadcastServerPacket(packet, Util.getOnlinePlayers());
}
@Override
public void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker) {
Preconditions.checkNotNull(packet, "packet cannot be NULL.");
Preconditions.checkNotNull(entity, "entity cannot be NULL.");
List<Player> trackers = getEntityTrackers(entity);
// Only add it if it's a player
if (includeTracker && entity instanceof Player) {
trackers.add((Player) entity);
}
broadcastServerPacket(packet, trackers);
}
@Override
public void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance) {
try {
// Square the maximum too
int maxDistance = maxObserverDistance * maxObserverDistance;
World world = origin.getWorld();
Location recycle = origin.clone();
// Only broadcast the packet to nearby players
for (Player player : Util.getOnlinePlayers()) {
if (world.equals(player.getWorld()) &&
getDistanceSquared(origin, recycle, player) <= maxDistance) {
sendServerPacket(player, packet);
}
}
} catch (InvocationTargetException e) {
throw new FieldAccessException("Unable to send server packet.", e);
}
}
/**
* Retrieve the squared distance between a location and a player.
* @param origin - the origin location.
* @param recycle - a location object to be recycled, if supported.
* @param player - the player.
* @return The squared distance between the player and the origin,
*/
private double getDistanceSquared(Location origin, Location recycle, Player player) {
if (hasRecycleDistance) {
try {
return player.getLocation(recycle).distanceSquared(origin);
} catch (Error e) {
// Damn it
hasRecycleDistance = false;
}
}
// The fallback method
return player.getLocation().distanceSquared(origin);
}
/**
* Broadcast a packet to a given iterable of players.
* @param packet - the packet to broadcast.
* @param players - the iterable of players.
*/
private void broadcastServerPacket(PacketContainer packet, Iterable<Player> players) {
try {
for (Player player : players) {
sendServerPacket(player, packet);
}
} catch (InvocationTargetException e) {
throw new FieldAccessException("Unable to send server packet.", e);
}
}
2012-10-15 00:31:55 +02:00
@Override
public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException {
sendServerPacket(reciever, packet, null, true);
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
sendServerPacket(reciever, packet, null, filters);
}
@Override
public void sendServerPacket(final Player receiver, final PacketContainer packet, NetworkMarker marker, final boolean filters) throws InvocationTargetException {
2013-12-05 21:24:31 +01:00
if (receiver == null)
throw new IllegalArgumentException("receiver cannot be NULL.");
2012-10-15 00:31:55 +02:00
if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL.");
if (packet.getType().getSender() == Sender.CLIENT)
throw new IllegalArgumentException("Packet of sender CLIENT cannot be sent to a client.");
// We may have to enable player injection indefinitely after this
if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING);
if (!filters) {
// We may have to delay the packet due to non-asynchronous monitor listeners
if (!Bukkit.isPrimaryThread() && playerInjection.hasMainThreadListener(packet.getType())) {
final NetworkMarker copy = marker;
server.getScheduler().scheduleSyncDelayedTask(library, new Runnable() {
@Override
public void run() {
try {
// Prevent infinite loops
if (!Bukkit.isPrimaryThread())
throw new IllegalStateException("Scheduled task was not executed on the main thread!");
sendServerPacket(receiver, packet, copy, filters);
} catch (Exception e) {
reporter.reportMinimal(library, "sendServerPacket-run()", e);
}
}
});
return;
}
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false);
sendingListeners.invokePacketSending(reporter, event, ListenerPriority.MONITOR);
marker = NetworkMarker.getNetworkMarker(event);
}
2013-12-05 21:24:31 +01:00
playerInjection.sendServerPacket(receiver, packet, marker, filters);
2012-10-15 00:31:55 +02:00
}
@Override
public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException {
WirePacket packet = new WirePacket(id, bytes);
sendWirePacket(receiver, packet);
}
@Override
public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException {
2016-03-19 21:01:38 +01:00
Channel channel = playerInjection.getChannel(receiver);
if (channel == null) {
throw new InvocationTargetException(new NullPointerException(), "Failed to obtain channel for " + receiver.getName());
}
channel.writeAndFlush(packet);
}
2012-10-15 00:31:55 +02:00
@Override
public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, true);
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException {
recieveClientPacket(sender, packet, null, filters);
}
@Override
public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException {
2012-10-15 00:31:55 +02:00
if (sender == null)
throw new IllegalArgumentException("sender cannot be NULL.");
if (packet == null)
throw new IllegalArgumentException("packet cannot be NULL.");
if (packet.getType().getSender() == Sender.SERVER)
throw new IllegalArgumentException("Packet of sender SERVER cannot be sent to the server.");
// And here too
if (packetCreation.compareAndSet(false, true))
incrementPhases(GamePhase.PLAYING);
Object mcPacket = packet.getHandle();
boolean cancelled = packetInjector.isCancelled(mcPacket);
2012-10-15 00:31:55 +02:00
// Make sure the packet isn't cancelled
if (cancelled) {
packetInjector.setCancelled(mcPacket, false);
}
2012-10-15 00:31:55 +02:00
if (filters) {
byte[] data = NetworkMarker.getByteBuffer(marker);
PacketEvent event = packetInjector.packetRecieved(packet, sender, data);
2012-10-15 00:31:55 +02:00
if (!event.isCancelled())
mcPacket = event.getPacket().getHandle();
else
return;
} else {
// Let the monitors know though
recievedListeners.invokePacketSending(
reporter,
PacketEvent.fromClient(this, packet, marker, sender, false),
ListenerPriority.MONITOR);
2012-10-15 00:31:55 +02:00
}
playerInjection.recieveClientPacket(sender, mcPacket);
// Let it stay cancelled
if (cancelled) {
packetInjector.setCancelled(mcPacket, true);
}
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
@Deprecated
2012-10-15 00:31:55 +02:00
public PacketContainer createPacket(int id) {
return createPacket(PacketType.findLegacy(id), true);
}
@Override
public PacketContainer createPacket(PacketType type) {
return createPacket(type, true);
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
@Deprecated
2012-10-15 00:31:55 +02:00
public PacketContainer createPacket(int id, boolean forceDefaults) {
return createPacket(PacketType.findLegacy(id), forceDefaults);
}
@Override
public PacketContainer createPacket(PacketType type, boolean forceDefaults) {
PacketContainer packet = new PacketContainer(type);
2012-10-15 00:31:55 +02:00
// Use any default values if possible
if (forceDefaults) {
try {
packet.getModifier().writeDefaults();
} catch (FieldAccessException e) {
throw new RuntimeException("Security exception.", e);
}
}
2012-10-15 00:31:55 +02:00
return packet;
}
2012-10-15 00:31:55 +02:00
@Override
@Deprecated
2012-10-15 00:31:55 +02:00
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
2012-10-15 00:31:55 +02:00
@Override
@Deprecated
2012-10-15 00:31:55 +02:00
public Set<Integer> getSendingFilters() {
return PacketRegistry.toLegacy(playerInjection.getSendingFilters());
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
public Set<Integer> getReceivingFilters() {
return PacketRegistry.toLegacy(packetInjector.getPacketHandlers());
}
@Override
public Set<PacketType> getSendingFilterTypes() {
return Collections.unmodifiableSet(playerInjection.getSendingFilters());
}
@Override
public Set<PacketType> getReceivingFilterTypes() {
return Collections.unmodifiableSet(packetInjector.getPacketHandlers());
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
@Override
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
EntityUtilities.updateEntity(entity, observers);
}
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
return EntityUtilities.getEntityFromID(container, id);
}
@Override
public List<Player> getEntityTrackers(Entity entity) throws FieldAccessException {
return EntityUtilities.getEntityTrackers(entity);
}
2012-10-15 00:31:55 +02:00
/**
* Initialize the packet injection for every player.
* @param players - list of players to inject.
2012-10-15 00:31:55 +02:00
*/
public void initializePlayers(List<Player> players) {
2012-10-15 00:31:55 +02:00
for (Player player : players)
playerInjection.injectPlayer(player, ConflictStrategy.OVERRIDE);
2012-10-15 00:31:55 +02:00
}
/**
* Uninitialize the packet injection of every player.
* @param players - list of players to uninject.
*/
public void uninitializePlayers(List<Player> players) {
for (Player player : players) {
playerInjection.uninjectPlayer(player);
}
}
2012-10-15 00:31:55 +02:00
/**
* Register this protocol manager on Bukkit.
*
2012-10-15 00:31:55 +02:00
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
*/
@Override
2012-10-15 00:31:55 +02:00
public void registerEvents(PluginManager manager, final Plugin plugin) {
if (spigotInjector != null && !spigotInjector.register(plugin))
throw new IllegalArgumentException("Spigot has already been registered.");
if (nettyInjector != null)
nettyInjector.inject();
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent event) {
PacketFilterManager.this.onPlayerLogin(event);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPrePlayerJoin(PlayerJoinEvent event) {
PacketFilterManager.this.onPrePlayerJoin(event);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerJoin(PlayerJoinEvent event) {
PacketFilterManager.this.onPlayerJoin(event);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(PlayerQuitEvent event) {
PacketFilterManager.this.onPlayerQuit(event);
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPluginDisabled(PluginDisableEvent event) {
PacketFilterManager.this.onPluginDisabled(event, plugin);
}
}, plugin);
2012-10-15 00:31:55 +02:00
}
private void onPlayerLogin(PlayerLoginEvent event) {
playerInjection.updatePlayer(event.getPlayer());
}
private void onPrePlayerJoin(PlayerJoinEvent event) {
playerInjection.updatePlayer(event.getPlayer());
}
private void onPlayerJoin(PlayerJoinEvent event) {
try {
// Let's clean up the other injection first.
playerInjection.uninjectPlayer(event.getPlayer().getAddress());
playerInjection.injectPlayer(event.getPlayer(), ConflictStrategy.OVERRIDE);
2013-10-28 19:09:11 +01:00
} catch (ServerHandlerNull e) {
// Caused by logged out players, or fake login events in MCPC+/Cauldron. Ignore it.
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_INJECT_PLAYER).callerParam(event).error(e)
);
}
}
private void onPlayerQuit(PlayerQuitEvent event) {
try {
Player player = event.getPlayer();
asyncFilterManager.removePlayer(player);
playerInjection.handleDisconnect(player);
playerInjection.uninjectPlayer(player);
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNINJECT_OFFLINE_PLAYER).callerParam(event).error(e)
);
}
}
private void onPluginDisabled(PluginDisableEvent event, Plugin protocolLibrary) {
try {
// Clean up in case the plugin forgets
if (event.getPlugin() != protocolLibrary) {
removePacketListeners(event.getPlugin());
}
} catch (Exception e) {
reporter.reportDetailed(PacketFilterManager.this,
Report.newBuilder(REPORT_CANNOT_UNREGISTER_PLUGIN).callerParam(event).error(e)
);
}
}
/**
* Retrieve the number of listeners that expect packets during playing.
* @return Number of listeners.
*/
private int getPhasePlayingCount() {
return phasePlayingCount.get();
}
/**
* Retrieve the number of listeners that expect packets during login.
* @return Number of listeners
*/
private int getPhaseLoginCount() {
return phaseLoginCount.get();
}
2012-10-15 00:31:55 +02:00
@Override
@Deprecated
public int getPacketID(Object packet) {
return PacketRegistry.getPacketID(packet.getClass());
}
@Override
public PacketType getPacketType(Object packet) {
2012-10-15 00:31:55 +02:00
if (packet == null)
throw new IllegalArgumentException("Packet cannot be NULL.");
if (!MinecraftReflection.isPacketClass(packet))
throw new IllegalArgumentException("The given object " + packet + " is not a packet.");
PacketType type = PacketRegistry.getPacketType(packet.getClass());
if (type != null) {
return type;
} else {
throw new IllegalArgumentException(
"Unable to find associated packet of " + packet + ": Lookup returned NULL.");
}
2012-10-15 00:31:55 +02:00
}
@Override
@Deprecated
public void registerPacketClass(Class<?> clazz, int packetID) {
PacketRegistry.getPacketToID().put(clazz, packetID);
}
@Override
@Deprecated
public void unregisterPacketClass(Class<?> clazz) {
PacketRegistry.getPacketToID().remove(clazz);
}
@Override
@Deprecated
public Class<?> getPacketClassFromID(int packetID, boolean forceVanilla) {
return PacketRegistry.getPacketClassFromID(packetID, forceVanilla);
}
2012-10-15 00:31:55 +02:00
/**
* Retrieve every known and supported server packet.
* @return An immutable set of every known server packet.
* @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft.
*/
@Deprecated
2012-10-15 00:31:55 +02:00
public static Set<Integer> getServerPackets() throws FieldAccessException {
return PacketRegistry.getServerPackets();
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
/**
* Retrieve every known and supported client packet.
* @return An immutable set of every known client packet.
* @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft.
*/
@Deprecated
2012-10-15 00:31:55 +02:00
public static Set<Integer> getClientPackets() throws FieldAccessException {
return PacketRegistry.getClientPackets();
2012-10-15 00:31:55 +02:00
}
2012-10-15 00:31:55 +02:00
/**
* Retrieves the current plugin class loader.
* @return Class loader.
*/
public ClassLoader getClassLoader() {
return classLoader;
}
2012-10-15 00:31:55 +02:00
@Override
public boolean isClosed() {
return hasClosed;
}
@Override
2012-10-15 00:31:55 +02:00
public void close() {
// Guard
if (hasClosed)
return;
// Remove packet handlers
if (packetInjector != null)
packetInjector.cleanupAll();
if (spigotInjector != null)
spigotInjector.cleanupAll();
if (nettyInjector != null)
nettyInjector.close();
2012-10-15 00:31:55 +02:00
// Remove server handler
playerInjection.close();
hasClosed = true;
2012-10-15 00:31:55 +02:00
// Remove listeners
packetListeners.clear();
2012-11-04 01:10:05 +01:00
recievedListeners = null;
sendingListeners = null;
// Also cleanup the interceptor for the write packet method
interceptWritePacket.cleanup();
2012-10-15 00:31:55 +02:00
// Clean up async handlers. We have to do this last.
asyncFilterManager.cleanupAll();
}
2012-10-15 00:31:55 +02:00
@Override
protected void finalize() throws Throwable {
close();
}
}