Cleaned up some code (#10).

This commit is contained in:
Jitse Boonstra
2019-02-15 19:59:38 +01:00
parent 7101542328
commit 06cec6c0bf
5 changed files with 121 additions and 541 deletions
@@ -20,11 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
* Represents a very tiny alternative to ProtocolLib. * Minimized version of TinyProtocol by Kristian suited for NPCLib.
* <p>
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
*
* @author Kristian
*/ */
public abstract class LegacyTinyProtocol { public abstract class LegacyTinyProtocol {
private static final AtomicInteger ID = new AtomicInteger(0); private static final AtomicInteger ID = new AtomicInteger(0);
@@ -56,7 +52,7 @@ public abstract class LegacyTinyProtocol {
private Listener listener; private Listener listener;
// Channels that have already been removed // Channels that have already been removed
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap()); private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
// List of network markers // List of network markers
private List<Object> networkManagers; private List<Object> networkManagers;
@@ -70,39 +66,32 @@ public abstract class LegacyTinyProtocol {
// Current handler name // Current handler name
private String handlerName; private String handlerName;
protected volatile boolean closed; private volatile boolean closed;
protected Plugin plugin; protected Plugin plugin;
/** protected LegacyTinyProtocol(final 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 LegacyTinyProtocol(final Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
// Compute handler name // Compute handler name
this.handlerName = getHandlerName(); this.handlerName = "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
// Prepare existing players // Prepare existing players
registerBukkitEvents(); registerBukkitEvents();
try { try {
System.out.println("Attempting to inject into netty"); System.out.println("[NPCLib] Attempting to inject into netty.");
registerChannelHandler(); registerChannelHandler();
registerPlayers(plugin); registerPlayers(plugin);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// Damn you, late bind // Damn you, late bind
plugin.getLogger().info("Attempting to delay injection."); plugin.getLogger().info("[NPCLib] Attempting to delay injection.");
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
registerChannelHandler(); registerChannelHandler();
registerPlayers(plugin); registerPlayers(plugin);
plugin.getLogger().info("Injection complete."); plugin.getLogger().info("[NPCLib] Injection complete.");
} }
}.runTask(plugin); }.runTask(plugin);
} }
@@ -112,6 +101,7 @@ public abstract class LegacyTinyProtocol {
// Handle connected channels // Handle connected channels
endInitProtocol = new ChannelInitializer<Channel>() { endInitProtocol = new ChannelInitializer<Channel>() {
@SuppressWarnings("all")
@Override @Override
protected void initChannel(Channel channel) throws Exception { protected void initChannel(Channel channel) throws Exception {
try { try {
@@ -123,7 +113,7 @@ public abstract class LegacyTinyProtocol {
} }
} }
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e); plugin.getLogger().log(Level.SEVERE, "[NPCLib] Cannot inject incomming channel " + channel, e);
} }
} }
@@ -132,6 +122,7 @@ public abstract class LegacyTinyProtocol {
// This is executed before Minecraft's channel handler // This is executed before Minecraft's channel handler
beginInitProtocol = new ChannelInitializer<Channel>() { beginInitProtocol = new ChannelInitializer<Channel>() {
@SuppressWarnings("all")
@Override @Override
protected void initChannel(Channel channel) throws Exception { protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(endInitProtocol); channel.pipeline().addLast(endInitProtocol);
@@ -141,6 +132,7 @@ public abstract class LegacyTinyProtocol {
serverChannelHandler = new ChannelInboundHandlerAdapter() { serverChannelHandler = new ChannelInboundHandlerAdapter() {
@SuppressWarnings("all")
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg; Channel channel = (Channel) msg;
@@ -152,12 +144,10 @@ public abstract class LegacyTinyProtocol {
}; };
} }
/**
* Register bukkit events.
*/
private void registerBukkitEvents() { private void registerBukkitEvents() {
listener = new Listener() { listener = new Listener() {
@SuppressWarnings("unused")
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public final void onPlayerLogin(PlayerJoinEvent e) { public final void onPlayerLogin(PlayerJoinEvent e) {
if (closed) if (closed)
@@ -171,6 +161,7 @@ public abstract class LegacyTinyProtocol {
} }
} }
@SuppressWarnings("unused")
@EventHandler @EventHandler
public final void onPluginDisable(PluginDisableEvent e) { public final void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) { if (e.getPlugin().equals(plugin)) {
@@ -205,9 +196,9 @@ public abstract class LegacyTinyProtocol {
Channel serverChannel = ((ChannelFuture) item).channel(); Channel serverChannel = ((ChannelFuture) item).channel();
serverChannels.add(serverChannel); serverChannels.add(serverChannel);
;
serverChannel.pipeline().addFirst(serverChannelHandler); serverChannel.pipeline().addFirst(serverChannelHandler);
System.out.println("Server channel handler injected (" + serverChannel + ")"); System.out.println("[NPCLib] Server channel handler injected (" + serverChannel + ")");
looking = false; looking = false;
} }
} }
@@ -237,118 +228,14 @@ public abstract class LegacyTinyProtocol {
} }
} }
/**
* Invoked when the server is starting to send a packet to a player.
* <p>
* Note that this is not executed on the main thread.
*
* @param receiver - the receiving player, NULL for early login/status packets.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player receiver, Object packet) {
return packet;
}
/**
* Invoked when the server has received a packet from a given player.
* <p>
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
*
* @param sender - the player that sent the packet, NULL for early login/status packets.
* @param packet - the packet being received.
* @return The packet to recieve instead, or NULL to cancel.
*/
public Object onPacketInAsync(Player sender, Object packet) { public Object onPacketInAsync(Player sender, Object packet) {
return packet; return packet;
} }
/** private void injectPlayer(Player player) {
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
*/
public void sendPacket(Player player, Object packet) {
sendPacket(getChannel(player), packet);
}
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet to send.
*/
public void sendPacket(Channel channel, Object packet) {
channel.pipeline().writeAndFlush(packet);
}
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
*
* @param player - the player that sent the packet.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Player player, Object packet) {
receivePacket(getChannel(player), packet);
}
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Channel channel, Object packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
/**
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
* <p>
* 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.
*/
protected String getHandlerName() {
return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
}
/**
* Add a custom channel handler to the given player's channel pipeline, 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.
*/
public void injectPlayer(Player player) {
injectChannelInternal(getChannel(player)).player = player; injectChannelInternal(getChannel(player)).player = player;
} }
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/
public void injectChannel(Channel channel) {
injectChannelInternal(channel);
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) { private PacketInterceptor injectChannelInternal(Channel channel) {
try { try {
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName); PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
@@ -367,13 +254,7 @@ public abstract class LegacyTinyProtocol {
} }
} }
/** private Channel getChannel(Player player) {
* Retrieve the Netty channel associated with a player. This is cached.
*
* @param player - the player.
* @return The Netty channel.
*/
public Channel getChannel(Player player) {
Channel channel = channelLookup.get(player.getName()); Channel channel = channelLookup.get(player.getName());
// Lookup channel again // Lookup channel again
@@ -387,37 +268,7 @@ public abstract class LegacyTinyProtocol {
return channel; return channel;
} }
public int getProtocolVersion(Player player) { private void uninjectChannel(final Channel channel) {
Channel channel = channelLookup.get(player.getName());
// Lookup channel again
if (channel == null) {
Object connection = getConnection.get(getPlayerHandle.invoke(player));
Object manager = getManager.get(connection);
channelLookup.put(player.getName(), channel = getChannel.get(manager));
}
return protocolLookup.get(channel);
}
/**
* Uninject a specific player.
*
* @param player - the injected player.
*/
public void uninjectPlayer(Player player) {
uninjectChannel(getChannel(player));
}
/**
* Uninject a specific channel.
* <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 // No need to guard against this if we're closing
if (!closed) { if (!closed) {
uninjectedChannels.add(channel); uninjectedChannels.add(channel);
@@ -427,36 +278,13 @@ public abstract class LegacyTinyProtocol {
channel.eventLoop().execute(() -> channel.pipeline().remove(handlerName)); channel.eventLoop().execute(() -> channel.pipeline().remove(handlerName));
} }
/** private void 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;
}
/**
* Cease listening for packets. This is called automatically when your plugin is disabled.
*/
public final void close() {
if (!closed) { if (!closed) {
closed = true; closed = true;
// Remove our handlers // Remove our handlers
for (Player player : plugin.getServer().getOnlinePlayers()) { for (Player player : plugin.getServer().getOnlinePlayers()) {
uninjectPlayer(player); uninjectChannel(getChannel(player));
} }
// Clean up Bukkit // Clean up Bukkit
@@ -465,11 +293,6 @@ public abstract class LegacyTinyProtocol {
} }
} }
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
* @author Kristian
*/
private final class PacketInterceptor extends ChannelDuplexHandler { private final class PacketInterceptor extends ChannelDuplexHandler {
// Updated by the login event // Updated by the login event
public volatile Player player; public volatile Player player;
@@ -491,25 +314,12 @@ public abstract class LegacyTinyProtocol {
try { try {
msg = onPacketInAsync(player, msg); msg = onPacketInAsync(player, msg);
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e); plugin.getLogger().log(Level.SEVERE, "[NPCLib] Error in onPacketInAsync().", e);
} }
if (msg != null) { if (msg != null) {
super.channelRead(ctx, msg); super.channelRead(ctx, msg);
} }
} }
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = onPacketOutAsync(player, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
} }
} }
@@ -21,11 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
* Represents a very tiny alternative to ProtocolLib. * Minimized version of TinyProtocol by Kristian suited for NPCLib.
* <p>
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
*
* @author Kristian
*/ */
public abstract class TinyProtocol { public abstract class TinyProtocol {
private static final AtomicInteger ID = new AtomicInteger(0); private static final AtomicInteger ID = new AtomicInteger(0);
@@ -53,7 +49,7 @@ public abstract class TinyProtocol {
private Listener listener; private Listener listener;
// Channels that have already been removed // Channels that have already been removed
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap()); private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
// List of network markers // List of network markers
private List<Object> networkManagers; private List<Object> networkManagers;
@@ -67,38 +63,32 @@ public abstract class TinyProtocol {
// Current handler name // Current handler name
private String handlerName; private String handlerName;
protected volatile boolean closed; private volatile boolean closed;
protected Plugin plugin; protected Plugin plugin;
/** protected TinyProtocol(final 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(final Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
// Compute handler name // Compute handler name
this.handlerName = getHandlerName(); this.handlerName = "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
// Prepare existing players // Prepare existing players
registerBukkitEvents(); registerBukkitEvents();
try { try {
System.out.println("[NPCLib] Attempting to inject into netty.");
registerChannelHandler(); registerChannelHandler();
registerPlayers(plugin); registerPlayers(plugin);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// Damn you, late bind // Damn you, late bind
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind."); plugin.getLogger().info("[NPCLib] Attempting to delay injection.");
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
registerChannelHandler(); registerChannelHandler();
registerPlayers(plugin); registerPlayers(plugin);
plugin.getLogger().info("[TinyProtocol] Late bind injection successful."); plugin.getLogger().info("[NPCLib] Injection complete.");
} }
}.runTask(plugin); }.runTask(plugin);
} }
@@ -108,6 +98,7 @@ public abstract class TinyProtocol {
// Handle connected channels // Handle connected channels
endInitProtocol = new ChannelInitializer<Channel>() { endInitProtocol = new ChannelInitializer<Channel>() {
@SuppressWarnings("all")
@Override @Override
protected void initChannel(Channel channel) throws Exception { protected void initChannel(Channel channel) throws Exception {
try { try {
@@ -119,7 +110,7 @@ public abstract class TinyProtocol {
} }
} }
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e); plugin.getLogger().log(Level.SEVERE, "[NPCLib] Cannot inject incomming channel " + channel, e);
} }
} }
@@ -128,6 +119,7 @@ public abstract class TinyProtocol {
// This is executed before Minecraft's channel handler // This is executed before Minecraft's channel handler
beginInitProtocol = new ChannelInitializer<Channel>() { beginInitProtocol = new ChannelInitializer<Channel>() {
@SuppressWarnings("all")
@Override @Override
protected void initChannel(Channel channel) throws Exception { protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(endInitProtocol); channel.pipeline().addLast(endInitProtocol);
@@ -137,6 +129,7 @@ public abstract class TinyProtocol {
serverChannelHandler = new ChannelInboundHandlerAdapter() { serverChannelHandler = new ChannelInboundHandlerAdapter() {
@SuppressWarnings("all")
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg; Channel channel = (Channel) msg;
@@ -149,12 +142,10 @@ public abstract class TinyProtocol {
}; };
} }
/**
* Register bukkit events.
*/
private void registerBukkitEvents() { private void registerBukkitEvents() {
listener = new Listener() { listener = new Listener() {
@SuppressWarnings("unused")
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public final void onPlayerLogin(PlayerLoginEvent e) { public final void onPlayerLogin(PlayerLoginEvent e) {
if (closed) if (closed)
@@ -168,6 +159,7 @@ public abstract class TinyProtocol {
} }
} }
@SuppressWarnings("unused")
@EventHandler @EventHandler
public final void onPluginDisable(PluginDisableEvent e) { public final void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) { if (e.getPlugin().equals(plugin)) {
@@ -195,7 +187,7 @@ public abstract class TinyProtocol {
List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection); List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
for (Object item : list) { for (Object item : list) {
if (!ChannelFuture.class.isInstance(item)) if (!(item instanceof ChannelFuture))
break; break;
// Channel future that contains the server connection // Channel future that contains the server connection
@@ -216,17 +208,12 @@ public abstract class TinyProtocol {
final ChannelPipeline pipeline = serverChannel.pipeline(); final ChannelPipeline pipeline = serverChannel.pipeline();
// Remove channel handler // Remove channel handler
serverChannel.eventLoop().execute(new Runnable() { serverChannel.eventLoop().execute(() -> {
try {
@Override pipeline.remove(serverChannelHandler);
public void run() { } catch (NoSuchElementException e) {
try { // That's fine
pipeline.remove(serverChannelHandler);
} catch (NoSuchElementException e) {
// That's fine
}
} }
}); });
} }
} }
@@ -237,120 +224,14 @@ public abstract class TinyProtocol {
} }
} }
/** public Object onPacketInAsync(Player sender, Object packet) {
* Invoked when the server is starting to send a packet to a player.
* <p>
* Note that this is not executed on the main thread.
*
* @param receiver - the receiving player, NULL for early login/status packets.
* @param channel - the channel that received the packet. Never NULL.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
return packet; return packet;
} }
/** private void injectPlayer(Player player) {
* Invoked when the server has received a packet from a given player.
* <p>
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
*
* @param sender - the player that sent the packet, NULL for early login/status packets.
* @param channel - channel that received the packet. Never NULL.
* @param packet - the packet being received.
* @return The packet to recieve instead, or NULL to cancel.
*/
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
return packet;
}
/**
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
*/
public void sendPacket(Player player, Object packet) {
sendPacket(getChannel(player), packet);
}
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet to send.
*/
public void sendPacket(Channel channel, Object packet) {
channel.pipeline().writeAndFlush(packet);
}
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the player that sent the packet.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Player player, Object packet) {
receivePacket(getChannel(player), packet);
}
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Channel channel, Object packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
/**
* Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
* <p>
* 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.
*/
protected String getHandlerName() {
return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
}
/**
* Add a custom channel handler to the given player's channel pipeline, 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.
*/
public void injectPlayer(Player player) {
injectChannelInternal(getChannel(player)).player = player; injectChannelInternal(getChannel(player)).player = player;
} }
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The intercepted channel, or NULL if it has already been injected.
*/
public void injectChannel(Channel channel) {
injectChannelInternal(channel);
}
/**
* Add a custom channel handler to the given channel.
*
* @param channel - the channel to inject.
* @return The packet interceptor.
*/
private PacketInterceptor injectChannelInternal(Channel channel) { private PacketInterceptor injectChannelInternal(Channel channel) {
try { try {
PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName); PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
@@ -369,13 +250,7 @@ public abstract class TinyProtocol {
} }
} }
/** private Channel getChannel(Player player) {
* Retrieve the Netty channel associated with a player. This is cached.
*
* @param player - the player.
* @return The Netty channel.
*/
public Channel getChannel(Player player) {
Channel channel = channelLookup.get(player.getName()); Channel channel = channelLookup.get(player.getName());
// Lookup channel again // Lookup channel again
@@ -389,69 +264,20 @@ public abstract class TinyProtocol {
return channel; return channel;
} }
/** private void close() {
* Uninject a specific player.
*
* @param player - the injected player.
*/
public void uninjectPlayer(Player player) {
uninjectChannel(getChannel(player));
}
/**
* Uninject a specific channel.
* <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);
}
});
}
/**
* 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;
}
/**
* Cease listening for packets. This is called automatically when your plugin is disabled.
*/
public final void close() {
if (!closed) { if (!closed) {
closed = true; closed = true;
// Remove our handlers // Remove our handlers
for (Player player : plugin.getServer().getOnlinePlayers()) { for (Player player : plugin.getServer().getOnlinePlayers()) {
uninjectPlayer(player); // No need to guard against this if we're closing
Channel channel = getChannel(player);
if (!closed) {
uninjectedChannels.add(channel);
}
// See ChannelInjector in ProtocolLib, line 590
channel.eventLoop().execute(() -> channel.pipeline().remove(handlerName));
} }
// Clean up Bukkit // Clean up Bukkit
@@ -460,11 +286,6 @@ public abstract class TinyProtocol {
} }
} }
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
* @author Kristian
*/
private final class PacketInterceptor extends ChannelDuplexHandler { private final class PacketInterceptor extends ChannelDuplexHandler {
// Updated by the login event // Updated by the login event
public volatile Player player; public volatile Player player;
@@ -476,9 +297,9 @@ public abstract class TinyProtocol {
handleLoginStart(channel, msg); handleLoginStart(channel, msg);
try { try {
msg = onPacketInAsync(player, channel, msg); msg = onPacketInAsync(player, msg);
} catch (Exception e) { } catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e); plugin.getLogger().log(Level.SEVERE, "[NPCLib] Error in onPacketInAsync().", e);
} }
if (msg != null) { if (msg != null) {
@@ -486,19 +307,6 @@ public abstract class TinyProtocol {
} }
} }
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = onPacketOutAsync(player, ctx.channel(), msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
private void handleLoginStart(Channel channel, Object packet) { private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) { if (PACKET_LOGIN_IN_START.isInstance(packet)) {
Object profile = getGameProfile.get(packet); Object profile = getGameProfile.get(packet);
@@ -6,7 +6,6 @@ package net.jitse.npclib;
import net.jitse.npclib.api.NPC; import net.jitse.npclib.api.NPC;
import net.jitse.npclib.listeners.ChunkListener; import net.jitse.npclib.listeners.ChunkListener;
import net.jitse.npclib.listeners.LegacyPacketListener;
import net.jitse.npclib.listeners.PacketListener; import net.jitse.npclib.listeners.PacketListener;
import net.jitse.npclib.listeners.PlayerListener; import net.jitse.npclib.listeners.PlayerListener;
import net.jitse.npclib.skin.Skin; import net.jitse.npclib.skin.Skin;
@@ -67,11 +66,7 @@ public class NPCLib {
pluginManager.registerEvents(new ChunkListener(), plugin); pluginManager.registerEvents(new ChunkListener(), plugin);
// Boot the according packet listener. // Boot the according packet listener.
if (Bukkit.getBukkitVersion().contains("1.7")) { new PacketListener().start(plugin, Bukkit.getBukkitVersion().contains("1.7"));
new LegacyPacketListener().start(plugin);
} else {
new PacketListener().start(plugin);
}
} }
/** /**
@@ -1,71 +0,0 @@
/*
* Copyright (c) 2018 Jitse Boonstra
*/
package net.jitse.npclib.listeners;
import com.comphenix.tinyprotocol.LegacyTinyProtocol;
import com.comphenix.tinyprotocol.Reflection;
import net.jitse.npclib.NPCManager;
import net.jitse.npclib.api.NPC;
import net.jitse.npclib.events.NPCInteractEvent;
import net.jitse.npclib.events.click.ClickType;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author Jitse Boonstra
*/
public class LegacyPacketListener {
// Classes:
private final Class<?> packetPlayInUseEntityClazz = Reflection.getMinecraftClass("PacketPlayInUseEntity");
// Fields:
private final Reflection.FieldAccessor entityIdField = Reflection.getField(packetPlayInUseEntityClazz, "a", int.class);
private final Reflection.FieldAccessor actionField = Reflection.getField(packetPlayInUseEntityClazz, "action", Object.class);
// Prevent players from clicking at very high speeds.
private final Set<UUID> delay = new HashSet<>();
public void start(JavaPlugin plugin) {
new LegacyTinyProtocol(plugin) {
@Override
public Object onPacketInAsync(Player player, Object packet) {
if (packetPlayInUseEntityClazz.isInstance(packet)) {
NPC npc = NPCManager.getAllNPCs().stream().filter(
check -> check.isActuallyShown(player) && check.getEntityId() == (int) entityIdField.get(packet))
.findFirst().orElse(null);
if (npc == null) {
// Default player, not doing magic with the packet.
return super.onPacketInAsync(player, packet);
}
if (delay.contains(player.getUniqueId())) {
return null;
}
ClickType clickType = actionField.get(packet).toString()
.equals("ATTACK") ? ClickType.LEFT_CLICK : ClickType.RIGHT_CLICK;
Bukkit.getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc));
UUID uuid = player.getUniqueId();
delay.add(uuid);
Bukkit.getScheduler().runTask(plugin, () -> delay.remove(uuid));
return null;
}
return super.onPacketInAsync(player, packet);
}
};
}
}
@@ -4,9 +4,9 @@
package net.jitse.npclib.listeners; package net.jitse.npclib.listeners;
import com.comphenix.tinyprotocol.LegacyTinyProtocol;
import com.comphenix.tinyprotocol.Reflection; import com.comphenix.tinyprotocol.Reflection;
import com.comphenix.tinyprotocol.TinyProtocol; import com.comphenix.tinyprotocol.TinyProtocol;
import io.netty.channel.Channel;
import net.jitse.npclib.NPCManager; import net.jitse.npclib.NPCManager;
import net.jitse.npclib.api.NPC; import net.jitse.npclib.api.NPC;
import net.jitse.npclib.events.NPCInteractEvent; import net.jitse.npclib.events.NPCInteractEvent;
@@ -34,39 +34,77 @@ public class PacketListener {
// Prevent players from clicking at very high speeds. // Prevent players from clicking at very high speeds.
private final Set<UUID> delay = new HashSet<>(); private final Set<UUID> delay = new HashSet<>();
public void start(JavaPlugin plugin) { public void start(JavaPlugin plugin, boolean is1_7) {
new TinyProtocol(plugin) { if (is1_7) {
// 1.7 R4 packet interaction.
new LegacyTinyProtocol(plugin) {
@Override @Override
public Object onPacketInAsync(Player player, Channel channel, Object packet) { public Object onPacketInAsync(Player player, Object packet) {
if (packetPlayInUseEntityClazz.isInstance(packet)) { if (packetPlayInUseEntityClazz.isInstance(packet)) {
NPC npc = NPCManager.getAllNPCs().stream().filter( NPC npc = NPCManager.getAllNPCs().stream().filter(
check -> check.isActuallyShown(player) && check.getEntityId() == (int) entityIdField.get(packet)) check -> check.isActuallyShown(player) && check.getEntityId() == (int) entityIdField.get(packet))
.findFirst().orElse(null); .findFirst().orElse(null);
if (npc == null) { if (npc == null) {
// Default player, not doing magic with the packet. // Default player, not doing magic with the packet.
return super.onPacketInAsync(player, channel, packet); return super.onPacketInAsync(player, packet);
} }
if (delay.contains(player.getUniqueId())) { if (delay.contains(player.getUniqueId())) {
return null;
}
ClickType clickType = actionField.get(packet).toString()
.equals("ATTACK") ? ClickType.LEFT_CLICK : ClickType.RIGHT_CLICK;
Bukkit.getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc));
UUID uuid = player.getUniqueId();
delay.add(uuid);
Bukkit.getScheduler().runTask(plugin, () -> delay.remove(uuid));
return null; return null;
} }
ClickType clickType = actionField.get(packet).toString() return super.onPacketInAsync(player, packet);
.equals("ATTACK") ? ClickType.LEFT_CLICK : ClickType.RIGHT_CLICK;
Bukkit.getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc));
UUID uuid = player.getUniqueId();
delay.add(uuid);
Bukkit.getScheduler().runTask(plugin, () -> delay.remove(uuid));
return null;
} }
};
} else {
// 1.8 (and above) packet interaction.
new TinyProtocol(plugin) {
return super.onPacketInAsync(player, channel, packet); @Override
} public Object onPacketInAsync(Player player, Object packet) {
};
if (packetPlayInUseEntityClazz.isInstance(packet)) {
NPC npc = NPCManager.getAllNPCs().stream().filter(
check -> check.isActuallyShown(player) && check.getEntityId() == (int) entityIdField.get(packet))
.findFirst().orElse(null);
if (npc == null) {
// Default player, not doing magic with the packet.
return super.onPacketInAsync(player, packet);
}
if (delay.contains(player.getUniqueId())) {
return null;
}
ClickType clickType = actionField.get(packet).toString()
.equals("ATTACK") ? ClickType.LEFT_CLICK : ClickType.RIGHT_CLICK;
Bukkit.getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc));
UUID uuid = player.getUniqueId();
delay.add(uuid);
Bukkit.getScheduler().runTask(plugin, () -> delay.remove(uuid));
return null;
}
return super.onPacketInAsync(player, packet);
}
};
}
} }
} }