mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2025-01-03 23:18:05 +01:00
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().
This commit is contained in:
parent
818ac5cbde
commit
d3e37df343
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
@ -36,7 +39,9 @@ import com.google.common.collect.MapMaker;
|
|||||||
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
|
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
|
||||||
* @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,17 +75,28 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createServerChannelHandler() {
|
private void createServerChannelHandler() {
|
||||||
@ -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;
|
* Uninject a specific channel.
|
||||||
injectPlayer(e.getPlayer());
|
* <p>
|
||||||
|
* 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);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
@EventHandler
|
}
|
||||||
public final void onPluginDisable(PluginDisableEvent e) {
|
|
||||||
if (e.getPlugin().equals(plugin)) {
|
/**
|
||||||
close();
|
* 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();
|
||||||
|
@ -6,4 +6,9 @@ author: Comphenix
|
|||||||
load: startup
|
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>
|
Loading…
Reference in New Issue
Block a user