mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-12-25 10:38:19 +01:00
Made the constructors for the different injectors more generic.
We now use builders. This should make it simpler to introduce Netty Spigot support.
This commit is contained in:
parent
7abfd2148e
commit
4685a06d27
@ -52,9 +52,10 @@ import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.*;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.packet.InjectorFactory;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectorBuilder;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
@ -181,10 +182,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
|
||||
try {
|
||||
// Initialize injection mangers
|
||||
this.playerInjection = new PlayerInjectionHandler(
|
||||
classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
|
||||
this.packetInjector = InjectorFactory.getInstance().createProxyInjector(
|
||||
classLoader, this, playerInjection, reporter);
|
||||
this.playerInjection = PlayerInjectorBuilder.newBuilder().
|
||||
invoker(this).
|
||||
server(server).
|
||||
reporter(reporter).
|
||||
classLoader(classLoader).
|
||||
packetListeners(packetListeners).
|
||||
injectionFilter(isInjectionNecessary).
|
||||
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
|
||||
|
@ -1,42 +0,0 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
|
||||
/**
|
||||
* A singleton factory for creating incoming packet injectors.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class InjectorFactory {
|
||||
private static final InjectorFactory INSTANCE = new InjectorFactory();
|
||||
|
||||
private InjectorFactory() {
|
||||
// No need to construct this
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the factory singleton.
|
||||
* @return Factory singleton.
|
||||
*/
|
||||
public static InjectorFactory getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packet injector that intercepts packets by overriding the packet registry.
|
||||
* @param classLoader - current class loader.
|
||||
* @param manager - packet invoker.
|
||||
* @param playerInjection - to lookup Player by DataInputStream.
|
||||
* @param reporter - error reporter.
|
||||
* @return A packet injector with these features.
|
||||
* @throws IllegalAccessException If we fail to create the injector.
|
||||
*/
|
||||
public PacketInjector createProxyInjector(
|
||||
ClassLoader classLoader, ListenerInvoker manager,
|
||||
PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException {
|
||||
|
||||
return new ProxyPacketInjector(classLoader, manager, playerInjection, reporter);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* A builder responsible for creating incoming packet injectors.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketInjectorBuilder {
|
||||
protected PacketInjectorBuilder() {
|
||||
// No need to construct this
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a new packet injector builder.
|
||||
* @return Injector builder.
|
||||
*/
|
||||
public static PacketInjectorBuilder newBuilder() {
|
||||
return new PacketInjectorBuilder();
|
||||
}
|
||||
|
||||
protected ClassLoader classLoader;
|
||||
protected ListenerInvoker invoker;
|
||||
protected ErrorReporter reporter;
|
||||
protected PlayerInjectionHandler playerInjection;
|
||||
|
||||
/**
|
||||
* Set the class loader to use during class generation.
|
||||
* @param classLoader - new class loader.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketInjectorBuilder classLoader(@Nonnull ClassLoader classLoader) {
|
||||
Preconditions.checkNotNull(classLoader, "classLoader cannot be NULL");
|
||||
this.classLoader = classLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error reporter used by the created injector.
|
||||
* @param reporter - new error reporter.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
|
||||
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
|
||||
this.reporter = reporter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packet stream invoker.
|
||||
* @param invoker - the invoker.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
|
||||
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
|
||||
this.invoker = invoker;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packet stream invoker.
|
||||
* @param invoker - the invoker.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Nonnull
|
||||
public PacketInjectorBuilder playerInjection(@Nonnull PlayerInjectionHandler playerInjection) {
|
||||
Preconditions.checkNotNull(playerInjection, "playerInjection cannot be NULL");
|
||||
this.playerInjection = playerInjection;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before an object is created with this builder.
|
||||
*/
|
||||
private void initializeDefaults() {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
// Initialize with default values if we can
|
||||
if (classLoader == null)
|
||||
classLoader = this.getClass().getClassLoader();
|
||||
if (reporter == null)
|
||||
reporter = ProtocolLibrary.getErrorReporter();
|
||||
if (invoker == null)
|
||||
invoker = (PacketFilterManager) manager;
|
||||
if (playerInjection == null)
|
||||
throw new IllegalStateException("Player injection parameter must be initialized.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packet injector using the provided fields or the default values.
|
||||
* <p>
|
||||
* Note that any non-null builder parameters must be set.
|
||||
* @return The created injector.
|
||||
* @throws IllegalAccessException If anything goes wrong in terms of reflection.
|
||||
*/
|
||||
public PacketInjector buildInjector() throws IllegalAccessException {
|
||||
initializeDefaults();
|
||||
return new ProxyPacketInjector(classLoader, invoker, playerInjection, reporter);
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ class NetLoginInjector {
|
||||
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||
|
||||
// Handles every hook
|
||||
private PlayerInjectionHandler injectionHandler;
|
||||
private ProxyPlayerInjectionHandler injectionHandler;
|
||||
private Server server;
|
||||
|
||||
// The current error rerporter
|
||||
@ -47,7 +47,7 @@ class NetLoginInjector {
|
||||
// Used to create fake players
|
||||
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
||||
|
||||
public NetLoginInjector(ErrorReporter reporter, PlayerInjectionHandler injectionHandler, Server server) {
|
||||
public NetLoginInjector(ErrorReporter reporter, ProxyPlayerInjectionHandler injectionHandler, Server server) {
|
||||
this.reporter = reporter;
|
||||
this.injectionHandler = injectionHandler;
|
||||
this.server = server;
|
||||
|
@ -1,211 +1,66 @@
|
||||
/*
|
||||
* 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
|
||||
* 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.
|
||||
* 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
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Responsible for injecting into a player's sendPacket method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PlayerInjectionHandler {
|
||||
/**
|
||||
* The maximum number of milliseconds to wait until a player can be looked up by connection.
|
||||
*/
|
||||
private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms
|
||||
|
||||
/**
|
||||
* The highest possible packet ID. It's unlikely that this value will ever change.
|
||||
*/
|
||||
private static final int MAXIMUM_PACKET_ID = 255;
|
||||
|
||||
// Server connection injection
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// NetLogin injector
|
||||
private NetLoginInjector netLoginInjector;
|
||||
|
||||
// The last successful player hook
|
||||
private PlayerInjector lastSuccessfulHook;
|
||||
|
||||
// Player injection
|
||||
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
|
||||
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
||||
|
||||
// Lookup player by connection
|
||||
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
|
||||
|
||||
// Player injection types
|
||||
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Whether or not we're closing
|
||||
private boolean hasClosed;
|
||||
|
||||
// Used to invoke events
|
||||
private ListenerInvoker invoker;
|
||||
|
||||
// Enabled packet filters
|
||||
private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
// List of packet listeners
|
||||
private Set<PacketListener> packetListeners;
|
||||
|
||||
// The class loader we're using
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Used to filter injection attempts
|
||||
private Predicate<GamePhase> injectionFilter;
|
||||
|
||||
public PlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
||||
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.reporter = reporter;
|
||||
this.invoker = invoker;
|
||||
this.injectionFilter = injectionFilter;
|
||||
this.packetListeners = packetListeners;
|
||||
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
|
||||
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
|
||||
serverInjection.injectList();
|
||||
}
|
||||
|
||||
public interface PlayerInjectionHandler {
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
public PlayerInjectHooks getPlayerHook() {
|
||||
return getPlayerHook(GamePhase.PLAYING);
|
||||
}
|
||||
|
||||
public abstract PlayerInjectHooks getPlayerHook();
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @param phase - the current game phase.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
|
||||
switch (phase) {
|
||||
case LOGIN:
|
||||
return loginPlayerHook;
|
||||
case PLAYING:
|
||||
return playingPlayerHook;
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
|
||||
}
|
||||
}
|
||||
public abstract PlayerInjectHooks getPlayerHook(GamePhase phase);
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
setPlayerHook(GamePhase.PLAYING, playerHook);
|
||||
}
|
||||
|
||||
public abstract void setPlayerHook(PlayerInjectHooks playerHook);
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param phase - the current game phase.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
|
||||
if (phase.hasLogin())
|
||||
loginPlayerHook = playerHook;
|
||||
if (phase.hasPlaying())
|
||||
playingPlayerHook = playerHook;
|
||||
|
||||
// Make sure the current listeners are compatible
|
||||
checkListener(packetListeners);
|
||||
}
|
||||
|
||||
public abstract void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook);
|
||||
|
||||
/**
|
||||
* Add an underlying packet handler of the given ID.
|
||||
* @param packetID - packet ID to register.
|
||||
*/
|
||||
public void addPacketHandler(int packetID) {
|
||||
sendingFilters.add(packetID);
|
||||
}
|
||||
|
||||
public abstract void addPacketHandler(int packetID);
|
||||
|
||||
/**
|
||||
* Remove an underlying packet handler of ths ID.
|
||||
* @param packetID - packet ID to unregister.
|
||||
*/
|
||||
public void removePacketHandler(int packetID) {
|
||||
sendingFilters.remove(packetID);
|
||||
}
|
||||
public abstract void removePacketHandler(int packetID);
|
||||
|
||||
/**
|
||||
* Used to construct a player hook.
|
||||
* @param player - the player to hook.
|
||||
* @param hook - the hook type.
|
||||
* @return A new player hoook
|
||||
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||
*/
|
||||
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
|
||||
// Construct the correct player hook
|
||||
switch (hook) {
|
||||
case NETWORK_HANDLER_FIELDS:
|
||||
return new NetworkFieldInjector(classLoader, reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_MANAGER_OBJECT:
|
||||
return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_SERVER_OBJECT:
|
||||
return new NetworkServerInjector(classLoader, reporter, player, invoker, sendingFilters, serverInjection);
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player by its DataInput connection.
|
||||
* @param inputStream - the associated DataInput connection.
|
||||
* @return The player.
|
||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||
*/
|
||||
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
||||
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public abstract Player getPlayerByConnection(DataInputStream inputStream)
|
||||
throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Retrieve a player by its DataInput connection.
|
||||
* @param inputStream - the associated DataInput connection.
|
||||
@ -214,243 +69,44 @@ public class PlayerInjectionHandler {
|
||||
* @return The player.
|
||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||
*/
|
||||
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
|
||||
// Wait until the connection owner has been established
|
||||
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit);
|
||||
|
||||
if (injector != null) {
|
||||
return injector.getPlayer();
|
||||
} else {
|
||||
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that retrieves the injector type of a given player injector.
|
||||
* @param injector - injector type.
|
||||
* @return The injector type.
|
||||
*/
|
||||
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
|
||||
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
|
||||
}
|
||||
|
||||
public abstract Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This call will be ignored if there's no listener that can receive the given events.
|
||||
* @param player - player to hook.
|
||||
*/
|
||||
public void injectPlayer(Player player) {
|
||||
// Inject using the player instance itself
|
||||
if (isInjectionNecessary(GamePhase.PLAYING)) {
|
||||
injectPlayer(player, player, GamePhase.PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void injectPlayer(Player player);
|
||||
|
||||
/**
|
||||
* Determine if it's truly necessary to perform the given player injection.
|
||||
* @param phase - current game phase.
|
||||
* @return TRUE if we should perform the injection, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInjectionNecessary(GamePhase phase) {
|
||||
return injectionFilter.apply(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This method will always perform the instructed injection.
|
||||
*
|
||||
* @param player - player to hook.
|
||||
* @param injectionPoint - the object to use during the injection process.
|
||||
* @param phase - the current game phase.
|
||||
* @return The resulting player injector, or NULL if the injection failed.
|
||||
*/
|
||||
PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) {
|
||||
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
|
||||
synchronized (player) {
|
||||
return injectPlayerInternal(player, injectionPoint, phase);
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe variant of the above
|
||||
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) {
|
||||
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
PlayerInjectHooks tempHook = getPlayerHook(phase);
|
||||
PlayerInjectHooks permanentHook = tempHook;
|
||||
|
||||
// The given player object may be fake, so be careful!
|
||||
|
||||
// See if we need to inject something else
|
||||
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
|
||||
public abstract boolean isInjectionNecessary(GamePhase phase);
|
||||
|
||||
// Don't inject if the class has closed
|
||||
if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) {
|
||||
while (tempHook != PlayerInjectHooks.NONE) {
|
||||
// Whether or not the current hook method failed completely
|
||||
boolean hookFailed = false;
|
||||
|
||||
// Remove the previous hook, if any
|
||||
cleanupHook(injector);
|
||||
|
||||
try {
|
||||
injector = getHookInstance(player, tempHook);
|
||||
|
||||
// Make sure this injection method supports the current game phase
|
||||
if (injector.canInject(phase)) {
|
||||
injector.initialize(injectionPoint);
|
||||
|
||||
DataInputStream inputStream = injector.getInputStream(false);
|
||||
|
||||
Socket socket = injector.getSocket();
|
||||
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
|
||||
|
||||
// Guard against NPE here too
|
||||
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
|
||||
|
||||
// Close any previously associated hooks before we proceed
|
||||
if (previous != null) {
|
||||
uninjectPlayer(previous.getPlayer(), false, true);
|
||||
}
|
||||
|
||||
injector.injectManager();
|
||||
|
||||
if (inputStream != null)
|
||||
dataInputLookup.put(inputStream, injector);
|
||||
if (address != null)
|
||||
addressLookup.put(address, injector);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
throw e;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Mark this injection attempt as a failure
|
||||
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
|
||||
e, player, injectionPoint, phase);
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Choose the previous player hook type
|
||||
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
|
||||
|
||||
if (hookFailed)
|
||||
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
|
||||
|
||||
// Check for UTTER FAILURE
|
||||
if (tempHook == PlayerInjectHooks.NONE) {
|
||||
cleanupHook(injector);
|
||||
injector = null;
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Should we set the default hook method too?
|
||||
if (hookFailed) {
|
||||
permanentHook = tempHook;
|
||||
}
|
||||
}
|
||||
|
||||
// Update values
|
||||
if (injector != null)
|
||||
lastSuccessfulHook = injector;
|
||||
if (permanentHook != getPlayerHook(phase))
|
||||
setPlayerHook(phase, tempHook);
|
||||
|
||||
// Save injector
|
||||
if (injector != null) {
|
||||
playerInjection.put(player, injector);
|
||||
}
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
private void cleanupHook(PlayerInjector injector) {
|
||||
// Clean up as much as possible
|
||||
try {
|
||||
if (injector != null)
|
||||
injector.cleanupAll();
|
||||
} catch (Exception ex) {
|
||||
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||
* @param player - player to process.
|
||||
*/
|
||||
public void handleDisconnect(Player player) {
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
if (injector != null) {
|
||||
injector.handleDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void handleDisconnect(Player player);
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
return uninjectPlayer(player, true, false);
|
||||
}
|
||||
|
||||
public abstract boolean uninjectPlayer(Player player);
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
|
||||
return uninjectPlayer(player, removeAuxiliary, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) {
|
||||
if (!hasClosed && player != null) {
|
||||
|
||||
PlayerInjector injector = playerInjection.remove(player);
|
||||
public abstract boolean uninjectPlayer(Player player, boolean removeAuxiliary);
|
||||
|
||||
if (injector != null) {
|
||||
InetSocketAddress address = player.getAddress();
|
||||
injector.cleanupAll();
|
||||
|
||||
// Remove the "hooked" network manager in our instance as well
|
||||
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
|
||||
try {
|
||||
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
|
||||
dummyInjector.initializePlayer(player);
|
||||
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// Let the user know
|
||||
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (removeAuxiliary) {
|
||||
// Note that the dataInputLookup will clean itself
|
||||
if (address != null)
|
||||
addressLookup.remove(address);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a player by the given address.
|
||||
* <p>
|
||||
@ -460,19 +116,8 @@ public class PlayerInjectionHandler {
|
||||
* @param address - address of the player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||
if (!hasClosed && address != null) {
|
||||
PlayerInjector injector = addressLookup.get(address);
|
||||
|
||||
// Clean up
|
||||
if (injector != null)
|
||||
uninjectPlayer(injector.getPlayer(), false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract boolean uninjectPlayer(InetSocketAddress address);
|
||||
|
||||
/**
|
||||
* Send the given packet to the given reciever.
|
||||
* @param reciever - the player receiver.
|
||||
@ -480,20 +125,9 @@ public class PlayerInjectionHandler {
|
||||
* @param filters - whether or not to invoke the packet filters.
|
||||
* @throws InvocationTargetException If an error occured during sending.
|
||||
*/
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||
PlayerInjector injector = getInjector(reciever);
|
||||
|
||||
// Send the packet, or drop it completely
|
||||
if (injector != null) {
|
||||
injector.sendServerPacket(packet.getHandle(), filters);
|
||||
} else {
|
||||
throw new PlayerLoggedOutException(String.format(
|
||||
"Unable to send packet %s (%s): Player %s has logged out.",
|
||||
packet.getID(), packet, reciever.getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||
throws InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Process a packet as if it were sent by the given player.
|
||||
* @param player - the sender.
|
||||
@ -501,158 +135,37 @@ public class PlayerInjectionHandler {
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @throws InvocationTargetException If the underlying method caused an error.
|
||||
*/
|
||||
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
// Process the given packet, or simply give up
|
||||
if (injector != null)
|
||||
injector.processPacket(mcPacket);
|
||||
else
|
||||
throw new PlayerLoggedOutException(String.format(
|
||||
"Unable to receieve packet %s. Player %s has logged out.",
|
||||
mcPacket, player.getName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injector associated with this player.
|
||||
* @param player - the player to find.
|
||||
* @return The injector, or NULL if not found.
|
||||
*/
|
||||
private PlayerInjector getInjector(Player player) {
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
|
||||
if (injector == null) {
|
||||
// Try getting it from the player itself
|
||||
if (player instanceof InjectContainer)
|
||||
return ((InjectContainer) player).getInjector();
|
||||
else
|
||||
return searchAddressLookup(player);
|
||||
} else {
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an injector by looking through the address map.
|
||||
* @param player - player to find.
|
||||
* @return The injector, or NULL if not found.
|
||||
*/
|
||||
private PlayerInjector searchAddressLookup(Player player) {
|
||||
// See if we can find it anywhere
|
||||
for (PlayerInjector injector : addressLookup.values()) {
|
||||
if (player.equals(injector.getUpdatedPlayer())) {
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player injector by looking for its NetworkManager.
|
||||
* @param networkManager - current network manager.
|
||||
* @return Related player injector.
|
||||
*/
|
||||
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
|
||||
// That's not legal
|
||||
if (networkManager == null)
|
||||
return null;
|
||||
|
||||
// O(n) is okay in this instance. This is only a backup solution.
|
||||
for (PlayerInjector injector : playerInjection.values()) {
|
||||
if (injector.getNetworkManager() == networkManager)
|
||||
return injector;
|
||||
}
|
||||
|
||||
// None found
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract void processPacket(Player player, Object mcPacket)
|
||||
throws IllegalAccessException, InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Determine if the given listeners are valid.
|
||||
* @param listeners - listeners to check.
|
||||
*/
|
||||
public void checkListener(Set<PacketListener> listeners) {
|
||||
// Make sure the current listeners are compatible
|
||||
if (lastSuccessfulHook != null) {
|
||||
for (PacketListener listener : listeners) {
|
||||
checkListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void checkListener(Set<PacketListener> listeners);
|
||||
|
||||
/**
|
||||
* Determine if a listener is valid or not.
|
||||
* <p>
|
||||
* If not, a warning will be printed to the console.
|
||||
* @param listener - listener to check.
|
||||
*/
|
||||
public void checkListener(PacketListener listener) {
|
||||
if (lastSuccessfulHook != null) {
|
||||
UnsupportedListener result = lastSuccessfulHook.checkListener(listener);
|
||||
public abstract void checkListener(PacketListener listener);
|
||||
|
||||
// We won't prevent the listener, as it may still have valid packets
|
||||
if (result != null) {
|
||||
reporter.reportWarning(this, "Cannot fully register listener for " +
|
||||
PacketAdapter.getPluginName(listener) + ": " + result.toString());
|
||||
|
||||
// These are illegal
|
||||
for (int packetID : result.getPackets())
|
||||
removePacketHandler(packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current list of registered sending listeners.
|
||||
* @return List of the sending listeners's packet IDs.
|
||||
*/
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return sendingFilters.toSet();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed || playerInjection == null)
|
||||
return;
|
||||
public abstract Set<Integer> getSendingFilters();
|
||||
|
||||
// Remove everything
|
||||
for (PlayerInjector injection : playerInjection.values()) {
|
||||
if (injection != null) {
|
||||
injection.cleanupAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove server handler
|
||||
if (serverInjection != null)
|
||||
serverInjection.cleanupAll();
|
||||
if (netLoginInjector != null)
|
||||
netLoginInjector.cleanupAll();
|
||||
serverInjection = null;
|
||||
netLoginInjector = null;
|
||||
hasClosed = true;
|
||||
|
||||
playerInjection.clear();
|
||||
addressLookup.clear();
|
||||
invoker = null;
|
||||
}
|
||||
/**
|
||||
* Close any lingering proxy injections.
|
||||
*/
|
||||
public abstract void close();
|
||||
|
||||
/**
|
||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
||||
* @param player - the player to update.
|
||||
*/
|
||||
public void scheduleDataInputRefresh(Player player) {
|
||||
final PlayerInjector injector = getInjector(player);
|
||||
|
||||
// Update the DataInputStream
|
||||
if (injector != null) {
|
||||
injector.scheduleAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dataInputLookup.put(injector.getInputStream(false), injector);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
public abstract void scheduleDataInputRefresh(Player player);
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
* Constructor for different player injectors.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PlayerInjectorBuilder {
|
||||
public static PlayerInjectorBuilder newBuilder() {
|
||||
return new PlayerInjectorBuilder();
|
||||
}
|
||||
|
||||
protected PlayerInjectorBuilder() {
|
||||
// Use the static method.
|
||||
}
|
||||
|
||||
protected ClassLoader classLoader;
|
||||
protected ErrorReporter reporter;
|
||||
protected Predicate<GamePhase> injectionFilter;
|
||||
protected ListenerInvoker invoker;
|
||||
protected Set<PacketListener> packetListeners;
|
||||
protected Server server;
|
||||
|
||||
/**
|
||||
* Set the class loader to use during class generation.
|
||||
* @param classLoader - new class loader.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder classLoader(@Nonnull ClassLoader classLoader) {
|
||||
Preconditions.checkNotNull(classLoader, "classLoader cannot be NULL");
|
||||
this.classLoader = classLoader;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error reporter used by the created injector.
|
||||
* @param reporter - new error reporter.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder reporter(@Nonnull ErrorReporter reporter) {
|
||||
Preconditions.checkNotNull(reporter, "reporter cannot be NULL");
|
||||
this.reporter = reporter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The injection filter that is used to determine if it is necessary to perform
|
||||
* injection during a certain phase.
|
||||
* @param injectionFilter - filter predicate.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Nonnull
|
||||
public PlayerInjectorBuilder injectionFilter(@Nonnull Predicate<GamePhase> injectionFilter) {
|
||||
Preconditions.checkNotNull(injectionFilter, "injectionFilter cannot be NULL");
|
||||
this.injectionFilter = injectionFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The packet stream invoker.
|
||||
* @param invoker - the invoker.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder invoker(@Nonnull ListenerInvoker invoker) {
|
||||
Preconditions.checkNotNull(invoker, "invoker cannot be NULL");
|
||||
this.invoker = invoker;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the set of packet listeners.
|
||||
* @param packetListeners - packet listeners.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
@Nonnull
|
||||
public PlayerInjectorBuilder packetListeners(@Nonnull Set<PacketListener> packetListeners) {
|
||||
Preconditions.checkNotNull(packetListeners, "packetListeners cannot be NULL");
|
||||
this.packetListeners = packetListeners;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Bukkit server used for scheduling.
|
||||
* @param server - the Bukkit server.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PlayerInjectorBuilder server(@Nonnull Server server) {
|
||||
Preconditions.checkNotNull(server, "server cannot be NULL");
|
||||
this.server = server;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before an object is created with this builder.
|
||||
*/
|
||||
private void initializeDefaults() {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
// Initialize with default values if we can
|
||||
if (classLoader == null)
|
||||
classLoader = this.getClass().getClassLoader();
|
||||
if (reporter == null)
|
||||
reporter = ProtocolLibrary.getErrorReporter();
|
||||
if (invoker == null)
|
||||
invoker = (PacketFilterManager) manager;
|
||||
if (server == null)
|
||||
server = Bukkit.getServer();
|
||||
if (injectionFilter == null)
|
||||
throw new IllegalStateException("injectionFilter must be initialized.");
|
||||
if (packetListeners == null)
|
||||
throw new IllegalStateException("packetListeners must be initialized.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the injection handler.
|
||||
* <p>
|
||||
* Any builder parameter marked as NON-NULL is essential and must be initialized.
|
||||
* @return The constructed injection handler using the current parameters.
|
||||
*/
|
||||
public PlayerInjectionHandler buildHandler() {
|
||||
// Fill any default fields
|
||||
initializeDefaults();
|
||||
|
||||
return new ProxyPlayerInjectionHandler(classLoader, reporter, injectionFilter, invoker,
|
||||
packetListeners, server);
|
||||
}
|
||||
}
|
@ -0,0 +1,679 @@
|
||||
/*
|
||||
* 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
|
||||
* 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.
|
||||
* 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
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.injector.player;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.concurrency.BlockingHashMap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Responsible for injecting into a player's sendPacket method.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
/**
|
||||
* The maximum number of milliseconds to wait until a player can be looked up by connection.
|
||||
*/
|
||||
private static final long TIMEOUT_PLAYER_LOOKUP = 2000; // ms
|
||||
|
||||
/**
|
||||
* The highest possible packet ID. It's unlikely that this value will ever change.
|
||||
*/
|
||||
private static final int MAXIMUM_PACKET_ID = 255;
|
||||
|
||||
// Server connection injection
|
||||
private InjectedServerConnection serverInjection;
|
||||
|
||||
// NetLogin injector
|
||||
private NetLoginInjector netLoginInjector;
|
||||
|
||||
// The last successful player hook
|
||||
private PlayerInjector lastSuccessfulHook;
|
||||
|
||||
// Player injection
|
||||
private Map<SocketAddress, PlayerInjector> addressLookup = Maps.newConcurrentMap();
|
||||
private Map<Player, PlayerInjector> playerInjection = Maps.newConcurrentMap();
|
||||
|
||||
// Lookup player by connection
|
||||
private BlockingHashMap<DataInputStream, PlayerInjector> dataInputLookup = BlockingHashMap.create();
|
||||
|
||||
// Player injection types
|
||||
private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT;
|
||||
|
||||
// Error reporter
|
||||
private ErrorReporter reporter;
|
||||
|
||||
// Whether or not we're closing
|
||||
private boolean hasClosed;
|
||||
|
||||
// Used to invoke events
|
||||
private ListenerInvoker invoker;
|
||||
|
||||
// Enabled packet filters
|
||||
private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
// List of packet listeners
|
||||
private Set<PacketListener> packetListeners;
|
||||
|
||||
// The class loader we're using
|
||||
private ClassLoader classLoader;
|
||||
|
||||
// Used to filter injection attempts
|
||||
private Predicate<GamePhase> injectionFilter;
|
||||
|
||||
public ProxyPlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate<GamePhase> injectionFilter,
|
||||
ListenerInvoker invoker, Set<PacketListener> packetListeners, Server server) {
|
||||
|
||||
this.classLoader = classLoader;
|
||||
this.reporter = reporter;
|
||||
this.invoker = invoker;
|
||||
this.injectionFilter = injectionFilter;
|
||||
this.packetListeners = packetListeners;
|
||||
this.netLoginInjector = new NetLoginInjector(reporter, this, server);
|
||||
this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector);
|
||||
serverInjection.injectList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public PlayerInjectHooks getPlayerHook() {
|
||||
return getPlayerHook(GamePhase.PLAYING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how the server packets are read.
|
||||
* @param phase - the current game phase.
|
||||
* @return Injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
|
||||
switch (phase) {
|
||||
case LOGIN:
|
||||
return loginPlayerHook;
|
||||
case PLAYING:
|
||||
return playingPlayerHook;
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
||||
setPlayerHook(GamePhase.PLAYING, playerHook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how the server packets are read.
|
||||
* @param phase - the current game phase.
|
||||
* @param playerHook - the new injection method for reading server packets.
|
||||
*/
|
||||
@Override
|
||||
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
|
||||
if (phase.hasLogin())
|
||||
loginPlayerHook = playerHook;
|
||||
if (phase.hasPlaying())
|
||||
playingPlayerHook = playerHook;
|
||||
|
||||
// Make sure the current listeners are compatible
|
||||
checkListener(packetListeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an underlying packet handler of the given ID.
|
||||
* @param packetID - packet ID to register.
|
||||
*/
|
||||
@Override
|
||||
public void addPacketHandler(int packetID) {
|
||||
sendingFilters.add(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an underlying packet handler of ths ID.
|
||||
* @param packetID - packet ID to unregister.
|
||||
*/
|
||||
@Override
|
||||
public void removePacketHandler(int packetID) {
|
||||
sendingFilters.remove(packetID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to construct a player hook.
|
||||
* @param player - the player to hook.
|
||||
* @param hook - the hook type.
|
||||
* @return A new player hoook
|
||||
* @throws IllegalAccessException Unable to do our reflection magic.
|
||||
*/
|
||||
private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException {
|
||||
// Construct the correct player hook
|
||||
switch (hook) {
|
||||
case NETWORK_HANDLER_FIELDS:
|
||||
return new NetworkFieldInjector(classLoader, reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_MANAGER_OBJECT:
|
||||
return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters);
|
||||
case NETWORK_SERVER_OBJECT:
|
||||
return new NetworkServerInjector(classLoader, reporter, player, invoker, sendingFilters, serverInjection);
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot construct a player injector.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player by its DataInput connection.
|
||||
* @param inputStream - the associated DataInput connection.
|
||||
* @return The player.
|
||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||
*/
|
||||
@Override
|
||||
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
||||
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player by its DataInput connection.
|
||||
* @param inputStream - the associated DataInput connection.
|
||||
* @param playerTimeout - the amount of time to wait for a result.
|
||||
* @param unit - unit of playerTimeout.
|
||||
* @return The player.
|
||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||
*/
|
||||
@Override
|
||||
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
|
||||
// Wait until the connection owner has been established
|
||||
PlayerInjector injector = dataInputLookup.get(inputStream, playerTimeout, unit);
|
||||
|
||||
if (injector != null) {
|
||||
return injector.getPlayer();
|
||||
} else {
|
||||
reporter.reportWarning(this, "Unable to find stream: " + inputStream);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that retrieves the injector type of a given player injector.
|
||||
* @param injector - injector type.
|
||||
* @return The injector type.
|
||||
*/
|
||||
private PlayerInjectHooks getInjectorType(PlayerInjector injector) {
|
||||
return injector != null ? injector.getHookType() : PlayerInjectHooks.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This call will be ignored if there's no listener that can receive the given events.
|
||||
* @param player - player to hook.
|
||||
*/
|
||||
@Override
|
||||
public void injectPlayer(Player player) {
|
||||
// Inject using the player instance itself
|
||||
if (isInjectionNecessary(GamePhase.PLAYING)) {
|
||||
injectPlayer(player, player, GamePhase.PLAYING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if it's truly necessary to perform the given player injection.
|
||||
* @param phase - current game phase.
|
||||
* @return TRUE if we should perform the injection, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isInjectionNecessary(GamePhase phase) {
|
||||
return injectionFilter.apply(phase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a player hook, allowing us to read server packets.
|
||||
* <p>
|
||||
* This method will always perform the instructed injection.
|
||||
*
|
||||
* @param player - player to hook.
|
||||
* @param injectionPoint - the object to use during the injection process.
|
||||
* @param phase - the current game phase.
|
||||
* @return The resulting player injector, or NULL if the injection failed.
|
||||
*/
|
||||
PlayerInjector injectPlayer(Player player, Object injectionPoint, GamePhase phase) {
|
||||
// Unfortunately, due to NetLoginHandler, multiple threads may potentially call this method.
|
||||
synchronized (player) {
|
||||
return injectPlayerInternal(player, injectionPoint, phase);
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe variant of the above
|
||||
private PlayerInjector injectPlayerInternal(Player player, Object injectionPoint, GamePhase phase) {
|
||||
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
PlayerInjectHooks tempHook = getPlayerHook(phase);
|
||||
PlayerInjectHooks permanentHook = tempHook;
|
||||
|
||||
// The given player object may be fake, so be careful!
|
||||
|
||||
// See if we need to inject something else
|
||||
boolean invalidInjector = injector != null ? !injector.canInject(phase) : true;
|
||||
|
||||
// Don't inject if the class has closed
|
||||
if (!hasClosed && player != null && (tempHook != getInjectorType(injector) || invalidInjector)) {
|
||||
while (tempHook != PlayerInjectHooks.NONE) {
|
||||
// Whether or not the current hook method failed completely
|
||||
boolean hookFailed = false;
|
||||
|
||||
// Remove the previous hook, if any
|
||||
cleanupHook(injector);
|
||||
|
||||
try {
|
||||
injector = getHookInstance(player, tempHook);
|
||||
|
||||
// Make sure this injection method supports the current game phase
|
||||
if (injector.canInject(phase)) {
|
||||
injector.initialize(injectionPoint);
|
||||
|
||||
DataInputStream inputStream = injector.getInputStream(false);
|
||||
|
||||
Socket socket = injector.getSocket();
|
||||
SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null;
|
||||
|
||||
// Guard against NPE here too
|
||||
PlayerInjector previous = address != null ? addressLookup.get(address) : null;
|
||||
|
||||
// Close any previously associated hooks before we proceed
|
||||
if (previous != null) {
|
||||
uninjectPlayer(previous.getPlayer(), false, true);
|
||||
}
|
||||
|
||||
injector.injectManager();
|
||||
|
||||
if (inputStream != null)
|
||||
dataInputLookup.put(inputStream, injector);
|
||||
if (address != null)
|
||||
addressLookup.put(address, injector);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (PlayerLoggedOutException e) {
|
||||
throw e;
|
||||
|
||||
} catch (Exception e) {
|
||||
// Mark this injection attempt as a failure
|
||||
reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.",
|
||||
e, player, injectionPoint, phase);
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Choose the previous player hook type
|
||||
tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1];
|
||||
|
||||
if (hookFailed)
|
||||
reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead.");
|
||||
|
||||
// Check for UTTER FAILURE
|
||||
if (tempHook == PlayerInjectHooks.NONE) {
|
||||
cleanupHook(injector);
|
||||
injector = null;
|
||||
hookFailed = true;
|
||||
}
|
||||
|
||||
// Should we set the default hook method too?
|
||||
if (hookFailed) {
|
||||
permanentHook = tempHook;
|
||||
}
|
||||
}
|
||||
|
||||
// Update values
|
||||
if (injector != null)
|
||||
lastSuccessfulHook = injector;
|
||||
if (permanentHook != getPlayerHook(phase))
|
||||
setPlayerHook(phase, tempHook);
|
||||
|
||||
// Save injector
|
||||
if (injector != null) {
|
||||
playerInjection.put(player, injector);
|
||||
}
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
|
||||
private void cleanupHook(PlayerInjector injector) {
|
||||
// Clean up as much as possible
|
||||
try {
|
||||
if (injector != null)
|
||||
injector.cleanupAll();
|
||||
} catch (Exception ex) {
|
||||
reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||
* @param player - player to process.
|
||||
*/
|
||||
@Override
|
||||
public void handleDisconnect(Player player) {
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
if (injector != null) {
|
||||
injector.handleDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
return uninjectPlayer(player, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
|
||||
return uninjectPlayer(player, removeAuxiliary, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given player.
|
||||
* @param player - player to unregister.
|
||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||
* @param prepareNextHook - whether or not we need to fix any lingering hooks.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) {
|
||||
if (!hasClosed && player != null) {
|
||||
|
||||
PlayerInjector injector = playerInjection.remove(player);
|
||||
|
||||
if (injector != null) {
|
||||
InetSocketAddress address = player.getAddress();
|
||||
injector.cleanupAll();
|
||||
|
||||
// Remove the "hooked" network manager in our instance as well
|
||||
if (prepareNextHook && injector instanceof NetworkObjectInjector) {
|
||||
try {
|
||||
PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT);
|
||||
dummyInjector.initializePlayer(player);
|
||||
dummyInjector.setNetworkManager(injector.getNetworkManager(), true);
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
// Let the user know
|
||||
reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (removeAuxiliary) {
|
||||
// Note that the dataInputLookup will clean itself
|
||||
if (address != null)
|
||||
addressLookup.remove(address);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a player by the given address.
|
||||
* <p>
|
||||
* If the server handler has been created before we've gotten a chance to unject the player,
|
||||
* the method will try a workaround to remove the injected hook in the NetServerHandler.
|
||||
*
|
||||
* @param address - address of the player to unregister.
|
||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
||||
if (!hasClosed && address != null) {
|
||||
PlayerInjector injector = addressLookup.get(address);
|
||||
|
||||
// Clean up
|
||||
if (injector != null)
|
||||
uninjectPlayer(injector.getPlayer(), false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given packet to the given reciever.
|
||||
* @param reciever - the player receiver.
|
||||
* @param packet - the packet to send.
|
||||
* @param filters - whether or not to invoke the packet filters.
|
||||
* @throws InvocationTargetException If an error occured during sending.
|
||||
*/
|
||||
@Override
|
||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
||||
PlayerInjector injector = getInjector(reciever);
|
||||
|
||||
// Send the packet, or drop it completely
|
||||
if (injector != null) {
|
||||
injector.sendServerPacket(packet.getHandle(), filters);
|
||||
} else {
|
||||
throw new PlayerLoggedOutException(String.format(
|
||||
"Unable to send packet %s (%s): Player %s has logged out.",
|
||||
packet.getID(), packet, reciever.getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a packet as if it were sent by the given player.
|
||||
* @param player - the sender.
|
||||
* @param mcPacket - the packet to process.
|
||||
* @throws IllegalAccessException If the reflection machinery failed.
|
||||
* @throws InvocationTargetException If the underlying method caused an error.
|
||||
*/
|
||||
@Override
|
||||
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
||||
|
||||
PlayerInjector injector = getInjector(player);
|
||||
|
||||
// Process the given packet, or simply give up
|
||||
if (injector != null)
|
||||
injector.processPacket(mcPacket);
|
||||
else
|
||||
throw new PlayerLoggedOutException(String.format(
|
||||
"Unable to receieve packet %s. Player %s has logged out.",
|
||||
mcPacket, player.getName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the injector associated with this player.
|
||||
* @param player - the player to find.
|
||||
* @return The injector, or NULL if not found.
|
||||
*/
|
||||
private PlayerInjector getInjector(Player player) {
|
||||
PlayerInjector injector = playerInjection.get(player);
|
||||
|
||||
if (injector == null) {
|
||||
// Try getting it from the player itself
|
||||
if (player instanceof InjectContainer)
|
||||
return ((InjectContainer) player).getInjector();
|
||||
else
|
||||
return searchAddressLookup(player);
|
||||
} else {
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an injector by looking through the address map.
|
||||
* @param player - player to find.
|
||||
* @return The injector, or NULL if not found.
|
||||
*/
|
||||
private PlayerInjector searchAddressLookup(Player player) {
|
||||
// See if we can find it anywhere
|
||||
for (PlayerInjector injector : addressLookup.values()) {
|
||||
if (player.equals(injector.getUpdatedPlayer())) {
|
||||
return injector;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player injector by looking for its NetworkManager.
|
||||
* @param networkManager - current network manager.
|
||||
* @return Related player injector.
|
||||
*/
|
||||
PlayerInjector getInjectorByNetworkHandler(Object networkManager) {
|
||||
// That's not legal
|
||||
if (networkManager == null)
|
||||
return null;
|
||||
|
||||
// O(n) is okay in this instance. This is only a backup solution.
|
||||
for (PlayerInjector injector : playerInjection.values()) {
|
||||
if (injector.getNetworkManager() == networkManager)
|
||||
return injector;
|
||||
}
|
||||
|
||||
// None found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given listeners are valid.
|
||||
* @param listeners - listeners to check.
|
||||
*/
|
||||
@Override
|
||||
public void checkListener(Set<PacketListener> listeners) {
|
||||
// Make sure the current listeners are compatible
|
||||
if (lastSuccessfulHook != null) {
|
||||
for (PacketListener listener : listeners) {
|
||||
checkListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a listener is valid or not.
|
||||
* <p>
|
||||
* If not, a warning will be printed to the console.
|
||||
* @param listener - listener to check.
|
||||
*/
|
||||
@Override
|
||||
public void checkListener(PacketListener listener) {
|
||||
if (lastSuccessfulHook != null) {
|
||||
UnsupportedListener result = lastSuccessfulHook.checkListener(listener);
|
||||
|
||||
// We won't prevent the listener, as it may still have valid packets
|
||||
if (result != null) {
|
||||
reporter.reportWarning(this, "Cannot fully register listener for " +
|
||||
PacketAdapter.getPluginName(listener) + ": " + result.toString());
|
||||
|
||||
// These are illegal
|
||||
for (int packetID : result.getPackets())
|
||||
removePacketHandler(packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current list of registered sending listeners.
|
||||
* @return List of the sending listeners's packet IDs.
|
||||
*/
|
||||
@Override
|
||||
public Set<Integer> getSendingFilters() {
|
||||
return sendingFilters.toSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Guard
|
||||
if (hasClosed || playerInjection == null)
|
||||
return;
|
||||
|
||||
// Remove everything
|
||||
for (PlayerInjector injection : playerInjection.values()) {
|
||||
if (injection != null) {
|
||||
injection.cleanupAll();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove server handler
|
||||
if (serverInjection != null)
|
||||
serverInjection.cleanupAll();
|
||||
if (netLoginInjector != null)
|
||||
netLoginInjector.cleanupAll();
|
||||
serverInjection = null;
|
||||
netLoginInjector = null;
|
||||
hasClosed = true;
|
||||
|
||||
playerInjection.clear();
|
||||
addressLookup.clear();
|
||||
invoker = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
||||
* @param player - the player to update.
|
||||
*/
|
||||
@Override
|
||||
public void scheduleDataInputRefresh(Player player) {
|
||||
final PlayerInjector injector = getInjector(player);
|
||||
|
||||
// Update the DataInputStream
|
||||
if (injector != null) {
|
||||
injector.scheduleAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
dataInputLookup.put(injector.getInputStream(false), injector);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user