Archiviert
13
0

Added the ability to toggle whether or not a given player is injected.

We also ensure we can run multiple instances of TinyProtocol without
requiring implementers to override getHandlerName(). 

Also fixed a potential memory leak, as the channel map was set to 
weakKeys() instead of the correct weakValues().
Dieser Commit ist enthalten in:
Kristian S. Stangeland 2014-05-17 17:10:17 +02:00
Ursprung 818ac5cbde
Commit d3e37df343
4 geänderte Dateien mit 145 neuen und 46 gelöschten Zeilen

Datei anzeigen

@ -1,6 +1,9 @@
package com.comphenix.tinyprotocol; package com.comphenix.tinyprotocol;
import org.bukkit.ChatColor;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -65,6 +68,26 @@ public class ExamplePlugin extends JavaPlugin {
}; };
} }
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player) {
Player player = (Player) sender;
// Toggle injection
if (protocol.hasInjected(player)) {
protocol.uninjectPlayer(player);
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
} else {
protocol.injectPlayer(player);
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
}
return true;
} else {
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
}
return false;
}
private void sendExplosion(Player player) { private void sendExplosion(Player player) {
try { try {
// Only visible for the client // Only visible for the client

Datei anzeigen

@ -9,8 +9,6 @@ import java.util.regex.Pattern;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
/** /**
* An utility class that simplifies reflection in Bukkit plugins. * An utility class that simplifies reflection in Bukkit plugins.
* @author Kristian * @author Kristian

Datei anzeigen

@ -1,7 +1,10 @@
package com.comphenix.tinyprotocol; package com.comphenix.tinyprotocol;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import net.minecraft.util.com.mojang.authlib.GameProfile; import net.minecraft.util.com.mojang.authlib.GameProfile;
@ -37,6 +40,8 @@ import com.google.common.collect.MapMaker;
* @author Kristian * @author Kristian
*/ */
public abstract class TinyProtocol { public abstract class TinyProtocol {
private static final AtomicInteger ID = new AtomicInteger(0);
// Used in order to lookup a channel // Used in order to lookup a channel
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class); private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
@ -55,9 +60,12 @@ public abstract class TinyProtocol {
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0); private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
// Speedup channel lookup // Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakKeys().makeMap(); private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
private Listener listener; private Listener listener;
// Channels that have already been removed
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap());
// List of network markers // List of network markers
private List<Object> networkManagers; private List<Object> networkManagers;
@ -67,15 +75,26 @@ public abstract class TinyProtocol {
private ChannelInitializer<Channel> beginInitProtocol; private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol; private ChannelInitializer<Channel> endInitProtocol;
// Current handler name
private String handlerName;
protected volatile boolean closed; protected volatile boolean closed;
protected Plugin plugin; protected Plugin plugin;
/**
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
* <p>
* You can construct multiple instances per plugin.
* @param plugin - the plugin.
*/
public TinyProtocol(Plugin plugin) { public TinyProtocol(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
this.plugin.getServer().getPluginManager().registerEvents(
listener = createListener(), plugin); // Compute handler name
this.handlerName = getHandlerName();
// Prepare existing players // Prepare existing players
registerBukkitEvents();
registerChannelHandler(); registerChannelHandler();
registerPlayers(plugin); registerPlayers(plugin);
} }
@ -91,7 +110,7 @@ public abstract class TinyProtocol {
// Stop injecting channels // Stop injecting channels
if (closed) if (closed)
return; return;
injectChannel(channel); injectChannelInternal(channel);
} }
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e); plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
@ -119,6 +138,34 @@ public abstract class TinyProtocol {
}; };
} }
/**
* Register bukkit events.
*/
private void registerBukkitEvents() {
listener = new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public final void onPlayerLogin(PlayerLoginEvent e) {
if (closed)
return;
Channel channel = getChannel(e.getPlayer());
// Don't inject players that have been explicitly uninjected
if (!uninjectedChannels.contains(channel)) {
injectChannelInternal(channel);
}
}
@EventHandler
public final void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) {
close();
}
}
};
this.plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void registerChannelHandler() { private void registerChannelHandler() {
Object mcServer = getMinecraftServer.get(Bukkit.getServer()); Object mcServer = getMinecraftServer.get(Bukkit.getServer());
@ -240,31 +287,41 @@ public abstract class TinyProtocol {
} }
/** /**
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name. * Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
* <p> * <p>
* Override this if you have multiple instances of TinyProtocol, and return a unique string per instance. * Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
* @return A unique channel handler name. * @return A unique channel handler name.
*/ */
protected String getHandlerName() { protected String getHandlerName() {
return "tiny-" + plugin.getName(); return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
} }
/** /**
* Add a custom channel handler to the given player's channel pipeline, * Add a custom channel handler to the given player's channel pipeline,
* allowing us to intercept sent and received packets. * allowing us to intercept sent and received packets.
* <p>
* This will automatically be called when a player has logged in.
* @param player - the player to inject. * @param player - the player to inject.
*/ */
private void injectPlayer(Player player) { public void injectPlayer(Player player) {
injectChannel(getChannel(player)).player = player; injectChannelInternal(getChannel(player)).player = player;
} }
/** /**
* Add a custom channel handler to the given channel. * Add a custom channel handler to the given channel.
* @param player - the channel to inject. * @param player - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/ */
private PacketInterceptor injectChannel(Channel channel) { public void injectChannel(Channel channel) {
String handlerName = getHandlerName(); injectChannelInternal(channel);
}
/**
* Add a custom channel handler to the given channel.
* @param player - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) {
try { try {
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName); PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
@ -272,6 +329,7 @@ public abstract class TinyProtocol {
if (interceptor == null) { if (interceptor == null) {
interceptor = new PacketInterceptor(); interceptor = new PacketInterceptor();
channel.pipeline().addBefore("packet_handler", handlerName, interceptor); channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
uninjectedChannels.remove(channel);
} }
return interceptor; return interceptor;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -285,7 +343,7 @@ public abstract class TinyProtocol {
* @param player - the player. * @param player - the player.
* @return The Netty channel. * @return The Netty channel.
*/ */
private Channel getChannel(Player player) { public Channel getChannel(Player player) {
Channel channel = channelLookup.get(player.getName()); Channel channel = channelLookup.get(player.getName());
// Lookup channel again // Lookup channel again
@ -299,24 +357,50 @@ public abstract class TinyProtocol {
} }
/** /**
* Create the Bukkit listener. * Uninject a specific player.
* @param player - the injected player.
*/ */
private Listener createListener() { public void uninjectPlayer(Player player) {
return new Listener() { uninjectChannel(getChannel(player));
@EventHandler(priority = EventPriority.LOWEST) }
public final void onPlayerLogin(PlayerLoginEvent e) {
if (closed)
return;
injectPlayer(e.getPlayer());
}
@EventHandler /**
public final void onPluginDisable(PluginDisableEvent e) { * Uninject a specific channel.
if (e.getPlugin().equals(plugin)) { * <p>
close(); * This will also disable the automatic channel injection that occurs when a player has properly logged in.
} * @param channel - the injected channel.
*/
public void uninjectChannel(final Channel channel) {
// No need to guard against this if we're closing
if (!closed) {
uninjectedChannels.add(channel);
}
// See ChannelInjector in ProtocolLib, line 590
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.pipeline().remove(handlerName);
} }
}; });
}
/**
* Determine if the given player has been injected by TinyProtocol.
* @param player - the player.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Player player) {
return hasInjected(getChannel(player));
}
/**
* Determine if the given channel has been injected by TinyProtocol.
* @param channel - the channel.
* @return TRUE if it is, FALSE otherwise.
*/
public boolean hasInjected(Channel channel) {
return channel.pipeline().get(handlerName) != null;
} }
/** /**
@ -325,22 +409,11 @@ public abstract class TinyProtocol {
public final void close() { public final void close() {
if (!closed) { if (!closed) {
closed = true; closed = true;
// Compute this once
final String handlerName = getHandlerName();
// Remove our handlers // Remove our handlers
for (Player player : plugin.getServer().getOnlinePlayers()) { for (Player player : plugin.getServer().getOnlinePlayers()) {
final Channel channel = getChannel(player); uninjectPlayer(player);
// See ChannelInjector in ProtocolLib, line 590
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.pipeline().remove(handlerName);
}
});
} }
// Clean up Bukkit // Clean up Bukkit
HandlerList.unregisterAll(listener); HandlerList.unregisterAll(listener);
unregisterChannelHandler(); unregisterChannelHandler();

Datei anzeigen

@ -7,3 +7,8 @@ load: startup
main: com.comphenix.tinyprotocol.ExamplePlugin main: com.comphenix.tinyprotocol.ExamplePlugin
database: false database: false
commands:
toggleinjection:
description: Toggle the TinyProtocol injection of the current player.
usage: /<command>