mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-12-25 18:47:52 +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.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.events.*;
|
import com.comphenix.protocol.events.*;
|
||||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||||
import com.comphenix.protocol.injector.packet.InjectorFactory;
|
import com.comphenix.protocol.injector.packet.PacketInjectorBuilder;
|
||||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
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.FieldAccessException;
|
||||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||||
@ -181,10 +182,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize injection mangers
|
// Initialize injection mangers
|
||||||
this.playerInjection = new PlayerInjectionHandler(
|
this.playerInjection = PlayerInjectorBuilder.newBuilder().
|
||||||
classLoader, reporter, isInjectionNecessary, this, packetListeners, server);
|
invoker(this).
|
||||||
this.packetInjector = InjectorFactory.getInstance().createProxyInjector(
|
server(server).
|
||||||
classLoader, this, playerInjection, reporter);
|
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);
|
this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this);
|
||||||
|
|
||||||
// Attempt to load the list of server and client packets
|
// 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();
|
private ConcurrentMap<Object, PlayerInjector> injectedLogins = Maps.newConcurrentMap();
|
||||||
|
|
||||||
// Handles every hook
|
// Handles every hook
|
||||||
private PlayerInjectionHandler injectionHandler;
|
private ProxyPlayerInjectionHandler injectionHandler;
|
||||||
private Server server;
|
private Server server;
|
||||||
|
|
||||||
// The current error rerporter
|
// The current error rerporter
|
||||||
@ -47,7 +47,7 @@ class NetLoginInjector {
|
|||||||
// Used to create fake players
|
// Used to create fake players
|
||||||
private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory();
|
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.reporter = reporter;
|
||||||
this.injectionHandler = injectionHandler;
|
this.injectionHandler = injectionHandler;
|
||||||
this.server = server;
|
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;
|
package com.comphenix.protocol.injector.player;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.bukkit.Server;
|
|
||||||
import org.bukkit.entity.Player;
|
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.PacketContainer;
|
||||||
import com.comphenix.protocol.events.PacketListener;
|
import com.comphenix.protocol.events.PacketListener;
|
||||||
import com.comphenix.protocol.injector.GamePhase;
|
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.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.
|
* Retrieves how the server packets are read.
|
||||||
* @return Injection method for reading server packets.
|
* @return Injection method for reading server packets.
|
||||||
*/
|
*/
|
||||||
public PlayerInjectHooks getPlayerHook() {
|
public abstract PlayerInjectHooks getPlayerHook();
|
||||||
return getPlayerHook(GamePhase.PLAYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves how the server packets are read.
|
* Retrieves how the server packets are read.
|
||||||
* @param phase - the current game phase.
|
* @param phase - the current game phase.
|
||||||
* @return Injection method for reading server packets.
|
* @return Injection method for reading server packets.
|
||||||
*/
|
*/
|
||||||
public PlayerInjectHooks getPlayerHook(GamePhase phase) {
|
public abstract 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.
|
* Sets how the server packets are read.
|
||||||
* @param playerHook - the new injection method for reading server packets.
|
* @param playerHook - the new injection method for reading server packets.
|
||||||
*/
|
*/
|
||||||
public void setPlayerHook(PlayerInjectHooks playerHook) {
|
public abstract void setPlayerHook(PlayerInjectHooks playerHook);
|
||||||
setPlayerHook(GamePhase.PLAYING, playerHook);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets how the server packets are read.
|
* Sets how the server packets are read.
|
||||||
* @param phase - the current game phase.
|
* @param phase - the current game phase.
|
||||||
* @param playerHook - the new injection method for reading server packets.
|
* @param playerHook - the new injection method for reading server packets.
|
||||||
*/
|
*/
|
||||||
public void setPlayerHook(GamePhase phase, PlayerInjectHooks playerHook) {
|
public abstract 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.
|
* Add an underlying packet handler of the given ID.
|
||||||
* @param packetID - packet ID to register.
|
* @param packetID - packet ID to register.
|
||||||
*/
|
*/
|
||||||
public void addPacketHandler(int packetID) {
|
public abstract void addPacketHandler(int packetID);
|
||||||
sendingFilters.add(packetID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an underlying packet handler of ths ID.
|
* Remove an underlying packet handler of ths ID.
|
||||||
* @param packetID - packet ID to unregister.
|
* @param packetID - packet ID to unregister.
|
||||||
*/
|
*/
|
||||||
public void removePacketHandler(int packetID) {
|
public abstract 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.
|
* Retrieve a player by its DataInput connection.
|
||||||
* @param inputStream - the associated DataInput connection.
|
* @param inputStream - the associated DataInput connection.
|
||||||
* @return The player.
|
* @return The player.
|
||||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||||
*/
|
*/
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream) throws InterruptedException {
|
public abstract Player getPlayerByConnection(DataInputStream inputStream)
|
||||||
return getPlayerByConnection(inputStream, TIMEOUT_PLAYER_LOOKUP, TimeUnit.MILLISECONDS);
|
throws InterruptedException;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a player by its DataInput connection.
|
* Retrieve a player by its DataInput connection.
|
||||||
* @param inputStream - the associated DataInput connection.
|
* @param inputStream - the associated DataInput connection.
|
||||||
@ -214,243 +69,44 @@ public class PlayerInjectionHandler {
|
|||||||
* @return The player.
|
* @return The player.
|
||||||
* @throws InterruptedException If the thread was interrupted during the wait.
|
* @throws InterruptedException If the thread was interrupted during the wait.
|
||||||
*/
|
*/
|
||||||
public Player getPlayerByConnection(DataInputStream inputStream, long playerTimeout, TimeUnit unit) throws InterruptedException {
|
public abstract 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.
|
* Initialize a player hook, allowing us to read server packets.
|
||||||
* <p>
|
* <p>
|
||||||
* This call will be ignored if there's no listener that can receive the given events.
|
* This call will be ignored if there's no listener that can receive the given events.
|
||||||
* @param player - player to hook.
|
* @param player - player to hook.
|
||||||
*/
|
*/
|
||||||
public void injectPlayer(Player player) {
|
public abstract 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.
|
* Determine if it's truly necessary to perform the given player injection.
|
||||||
* @param phase - current game phase.
|
* @param phase - current game phase.
|
||||||
* @return TRUE if we should perform the injection, FALSE otherwise.
|
* @return TRUE if we should perform the injection, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isInjectionNecessary(GamePhase phase) {
|
public abstract 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.
|
* Invoke special routines for handling disconnect before a player is uninjected.
|
||||||
* @param player - player to process.
|
* @param player - player to process.
|
||||||
*/
|
*/
|
||||||
public void handleDisconnect(Player player) {
|
public abstract void handleDisconnect(Player player);
|
||||||
PlayerInjector injector = getInjector(player);
|
|
||||||
|
|
||||||
if (injector != null) {
|
|
||||||
injector.handleDisconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters the given player.
|
* Unregisters the given player.
|
||||||
* @param player - player to unregister.
|
* @param player - player to unregister.
|
||||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean uninjectPlayer(Player player) {
|
public abstract boolean uninjectPlayer(Player player);
|
||||||
return uninjectPlayer(player, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters the given player.
|
* Unregisters the given player.
|
||||||
* @param player - player to unregister.
|
* @param player - player to unregister.
|
||||||
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
* @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address.
|
||||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean uninjectPlayer(Player player, boolean removeAuxiliary) {
|
public abstract 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.
|
* Unregisters a player by the given address.
|
||||||
* <p>
|
* <p>
|
||||||
@ -460,19 +116,8 @@ public class PlayerInjectionHandler {
|
|||||||
* @param address - address of the player to unregister.
|
* @param address - address of the player to unregister.
|
||||||
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
* @return TRUE if a player has been uninjected, FALSE otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean uninjectPlayer(InetSocketAddress address) {
|
public abstract 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.
|
* Send the given packet to the given reciever.
|
||||||
* @param reciever - the player receiver.
|
* @param reciever - the player receiver.
|
||||||
@ -480,20 +125,9 @@ public class PlayerInjectionHandler {
|
|||||||
* @param filters - whether or not to invoke the packet filters.
|
* @param filters - whether or not to invoke the packet filters.
|
||||||
* @throws InvocationTargetException If an error occured during sending.
|
* @throws InvocationTargetException If an error occured during sending.
|
||||||
*/
|
*/
|
||||||
public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException {
|
public abstract void sendServerPacket(Player reciever, PacketContainer packet, boolean filters)
|
||||||
PlayerInjector injector = getInjector(reciever);
|
throws InvocationTargetException;
|
||||||
|
|
||||||
// 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.
|
* Process a packet as if it were sent by the given player.
|
||||||
* @param player - the sender.
|
* @param player - the sender.
|
||||||
@ -501,158 +135,37 @@ public class PlayerInjectionHandler {
|
|||||||
* @throws IllegalAccessException If the reflection machinery failed.
|
* @throws IllegalAccessException If the reflection machinery failed.
|
||||||
* @throws InvocationTargetException If the underlying method caused an error.
|
* @throws InvocationTargetException If the underlying method caused an error.
|
||||||
*/
|
*/
|
||||||
public void processPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
|
public abstract 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.
|
* Determine if the given listeners are valid.
|
||||||
* @param listeners - listeners to check.
|
* @param listeners - listeners to check.
|
||||||
*/
|
*/
|
||||||
public void checkListener(Set<PacketListener> listeners) {
|
public abstract 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.
|
* Determine if a listener is valid or not.
|
||||||
* <p>
|
* <p>
|
||||||
* If not, a warning will be printed to the console.
|
* If not, a warning will be printed to the console.
|
||||||
* @param listener - listener to check.
|
* @param listener - listener to check.
|
||||||
*/
|
*/
|
||||||
public void checkListener(PacketListener listener) {
|
public abstract 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.
|
* Retrieve the current list of registered sending listeners.
|
||||||
* @return List of the sending listeners's packet IDs.
|
* @return List of the sending listeners's packet IDs.
|
||||||
*/
|
*/
|
||||||
public Set<Integer> getSendingFilters() {
|
public abstract Set<Integer> getSendingFilters();
|
||||||
return sendingFilters.toSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() {
|
|
||||||
// Guard
|
|
||||||
if (hasClosed || playerInjection == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Remove everything
|
/**
|
||||||
for (PlayerInjector injection : playerInjection.values()) {
|
* Close any lingering proxy injections.
|
||||||
if (injection != null) {
|
*/
|
||||||
injection.cleanupAll();
|
public abstract void close();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
* Inform the current PlayerInjector that it should update the DataInputStream next.
|
||||||
* @param player - the player to update.
|
* @param player - the player to update.
|
||||||
*/
|
*/
|
||||||
public void scheduleDataInputRefresh(Player player) {
|
public abstract 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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