diff --git a/SpigotCore_19/src/de/steamwar/core/ChatWrapper19.java b/SpigotCore_19/src/de/steamwar/core/ChatWrapper19.java index 8be810b..f9c1202 100644 --- a/SpigotCore_19/src/de/steamwar/core/ChatWrapper19.java +++ b/SpigotCore_19/src/de/steamwar/core/ChatWrapper19.java @@ -19,8 +19,6 @@ package de.steamwar.core; -import com.comphenix.tinyprotocol.Reflection; -import com.comphenix.tinyprotocol.TinyProtocol; import net.minecraft.network.chat.IChatMutableComponent; import net.minecraft.network.chat.contents.LiteralContents; @@ -29,10 +27,4 @@ public class ChatWrapper19 implements ChatWrapper { public Object stringToChatComponent(String text) { return IChatMutableComponent.a(new LiteralContents(text)); } - - private static final Reflection.FieldAccessor getName = Reflection.getField(TinyProtocol.PACKET_LOGIN_IN_START, String.class, 0); - @Override - public String getNameByLoginPacket(Object packet) { - return getName.get(packet); - } } diff --git a/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java b/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java index 7d02dfd..68b8e07 100644 --- a/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java +++ b/SpigotCore_8/src/de/steamwar/core/ChatWrapper8.java @@ -20,8 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.Reflection; -import com.comphenix.tinyprotocol.TinyProtocol; -import com.mojang.authlib.GameProfile; public class ChatWrapper8 implements ChatWrapper { @@ -30,10 +28,4 @@ public class ChatWrapper8 implements ChatWrapper { public Object stringToChatComponent(String text) { return chatComponentConstructor.invoke(text); } - - private static final Reflection.FieldAccessor getGameProfile = Reflection.getField(TinyProtocol.PACKET_LOGIN_IN_START, GameProfile.class, 0); - @Override - public String getNameByLoginPacket(Object packet) { - return getGameProfile.get(packet).getName(); - } } diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 82a4afb..caf68fa 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -2,252 +2,70 @@ package com.comphenix.tinyprotocol; import com.comphenix.tinyprotocol.Reflection.FieldAccessor; import com.comphenix.tinyprotocol.Reflection.MethodInvoker; -import com.google.common.collect.Lists; -import com.google.common.collect.MapMaker; -import de.steamwar.core.ChatWrapper; import de.steamwar.core.Core; -import io.netty.channel.*; -import org.bukkit.Bukkit; +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitRunnable; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.logging.Level; -/** - * Represents a very tiny alternative to ProtocolLib. - *

- * It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)! - * - * @author Kristian - */ -public class TinyProtocol { - private static final AtomicInteger ID = new AtomicInteger(0); - - // Used in order to lookup a channel - private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); - private static final Class playerConnection = Reflection.getUntypedClass("{nms.server.network}.PlayerConnection"); - private static final FieldAccessor getConnection = Reflection.getField("{nms.server.level}.EntityPlayer", playerConnection, 0); - private static final Class networkManager = Reflection.getUntypedClass("{nms.network}.NetworkManager"); - private static final FieldAccessor getManager = Reflection.getField(playerConnection, networkManager, 0); - private static final FieldAccessor getChannel = Reflection.getField(networkManager, Channel.class, 0); - - // Looking up ServerConnection - private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms.server}.MinecraftServer"); - private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms.server.network}.ServerConnection"); - private static final FieldAccessor getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0); - private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0); - private static final FieldAccessor getNetworkMarkers = Reflection.getField(serverConnectionClass, Collection.class, 1); - - // Packets we have to intercept - public static final Class PACKET_LOGIN_IN_START = Reflection.getClass("{nms.network.protocol.login}.PacketLoginInStart"); - - // Speedup channel lookup - private final Map channelLookup = new MapMaker().weakValues().makeMap(); - private final Listener listener; - - // Channels that have already been removed - private final Set uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); - - // List of network markers - private Collection networkManagers; - - // Injected channel handlers - private final List serverChannels = Lists.newArrayList(); - private ChannelInboundHandlerAdapter serverChannelHandler; - private ChannelInitializer beginInitProtocol; - private ChannelInitializer endInitProtocol; - - // Current handler name - private final String handlerName; - private volatile boolean closed; - private final Plugin plugin; - - private final Map, List>> packetFilters = new HashMap<>(); - - @Deprecated - private PacketFilter inFilter = (player, channel, packet) -> packet; - - @Deprecated - private PacketFilter outFilter = (player, channel, packet) -> packet; - +public class TinyProtocol implements Listener { public static final TinyProtocol instance = new TinyProtocol(Core.getInstance()); + private static int id = 0; public static void init() { //enforce init } - public TinyProtocol(final Plugin plugin) { + private final Plugin plugin; + private final String handlerName; + private boolean closed; + + private final Map, List>> packetFilters = new HashMap<>(); + private final Map playerInterceptors = new HashMap<>(); + + private TinyProtocol(final Plugin plugin) { this.plugin = plugin; // Compute handler name - this.handlerName = "tiny-" + plugin.getName() + "-" + ID.incrementAndGet(); + this.handlerName = "tiny-" + plugin.getName() + "-" + ++id; // Prepare existing players - listener = new Listener() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerLogin(PlayerLoginEvent e) { - injectPlayerOnLogin( - e.getPlayer(), - () -> Bukkit.getScheduler().runTask(plugin, () -> injectPlayerOnLogin( - e.getPlayer(), - () -> Bukkit.getScheduler().runTaskLater(plugin, () -> injectPlayerOnLogin( - e.getPlayer(), - () -> { - //Give up - } - ), 1) - )) - ); - } - - @EventHandler - public void onPluginDisable(PluginDisableEvent e) { - if (e.getPlugin().equals(plugin)) { - close(); - } - } - }; - plugin.getServer().getPluginManager().registerEvents(listener, plugin); - - try { - registerChannelHandler(); - registerPlayers(plugin); - } catch (IllegalArgumentException ex) { - // Damn you, late bind - plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind."); - - new BukkitRunnable() { - @Override - public void run() { - registerChannelHandler(); - registerPlayers(plugin); - plugin.getLogger().info("[TinyProtocol] Late bind injection successful."); - } - }.runTask(plugin); - } - } - - private void injectPlayerOnLogin(Player player, Runnable errorHandler) { - if (closed) - return; - - try { - Channel channel = getChannel(player); - - // Don't inject players that have been explicitly uninjected - if (!uninjectedChannels.contains(channel)) { - injectPlayer(player); - } - } catch (NullPointerException ex) { - errorHandler.run(); - } - } - - private void createServerChannelHandler() { - // Handle connected channels - endInitProtocol = new ChannelInitializer() { - - @Override - protected void initChannel(Channel channel) throws Exception { - try { - // This can take a while, so we need to stop the main thread from interfering - synchronized (networkManagers) { - // Stop injecting channels - if (!closed) { - channel.eventLoop().submit(() -> injectChannelInternal(channel)); - } - } - } catch (Exception e) { - plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e); - } - } - - }; - - // This is executed before Minecraft's channel handler - beginInitProtocol = new ChannelInitializer() { - - @Override - protected void initChannel(Channel channel) throws Exception { - channel.pipeline().addLast(endInitProtocol); - } - - }; - - serverChannelHandler = new ChannelInboundHandlerAdapter() { - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - Channel channel = (Channel) msg; - - // Prepare to initialize ths channel - channel.pipeline().addFirst(beginInitProtocol); - ctx.fireChannelRead(msg); - } - - }; - } - - @SuppressWarnings("unchecked") - private void registerChannelHandler() { - Object mcServer = getMinecraftServer.get(Bukkit.getServer()); - Object serverConnection = getServerConnection.get(mcServer); - boolean looking = true; - - // We need to synchronize against this list - networkManagers = getNetworkMarkers.get(serverConnection); - createServerChannelHandler(); - - // Find the correct list, or implicitly throw an exception - for (int i = 0; looking; i++) { - List list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection); - - for (Object item : list) { - if (!(item instanceof ChannelFuture)) - break; - - // Channel future that contains the server connection - Channel serverChannel = ((ChannelFuture) item).channel(); - - serverChannels.add(serverChannel); - serverChannel.pipeline().addFirst(serverChannelHandler); - looking = false; - } - } - } - - private void unregisterChannelHandler() { - if (serverChannelHandler == null) - return; - - for (Channel serverChannel : serverChannels) { - final ChannelPipeline pipeline = serverChannel.pipeline(); - - // Remove channel handler - serverChannel.eventLoop().execute(() -> { - try { - pipeline.remove(serverChannelHandler); - } catch (NoSuchElementException e) { - // That's fine - } - }); - } - } - - private void registerPlayers(Plugin plugin) { for (Player player : plugin.getServer().getOnlinePlayers()) { - injectPlayer(player); + new PacketInterceptor(player); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerLoginEvent e) { + if(closed) + return; + new PacketInterceptor(e.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerDisconnect(PlayerQuitEvent e) { + getInterceptor(e.getPlayer()).ifPresent(PacketInterceptor::close); + } + + @EventHandler + public void onPluginDisable(PluginDisableEvent e) { + if (e.getPlugin().equals(plugin)) { + close(); } } @@ -259,158 +77,87 @@ public class TinyProtocol { packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter); } - public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { - return filterPacket(outFilter, receiver, channel, packet); - } - - public Object onPacketInAsync(Player sender, Channel channel, Object packet) { - return filterPacket(inFilter, sender, channel, packet); - } - - private Object filterPacket(PacketFilter handler, Player player, Channel channel, Object packet) { - List> filters = packetFilters.getOrDefault(packet.getClass(), Collections.emptyList()); - - for(BiFunction filter : filters) { - packet = filter.apply(player, packet); - - if(packet == null) - return packet; - } - - return handler.onPacket(player, channel, packet); - } - - @Deprecated - public void setInFilter(PacketFilter filter) { - inFilter = filter; - } - - @Deprecated - public void setOutFilter(PacketFilter filter) { - outFilter = filter; - } - public void sendPacket(Player player, Object packet) { - sendPacket(getChannel(player), packet); - } - - public void sendPacket(Channel channel, Object packet) { - channel.pipeline().writeAndFlush(packet); + getInterceptor(player).ifPresent(i -> i.sendPacket(packet)); } public void receivePacket(Player player, Object packet) { - receivePacket(getChannel(player), packet); - } - - public void receivePacket(Channel channel, Object packet) { - channel.pipeline().context("encoder").fireChannelRead(packet); - } - - public void injectPlayer(Player player) { - injectChannelInternal(getChannel(player)).player = player; - } - - public void injectChannel(Channel channel) { - injectChannelInternal(channel); - } - - private PacketInterceptor injectChannelInternal(Channel channel) { - try { - PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName); - - // Inject our packet interceptor - if (interceptor == null) { - interceptor = new PacketInterceptor(); - if(!channel.isActive()) - return interceptor; - - channel.pipeline().addBefore("packet_handler", handlerName, interceptor); - uninjectedChannels.remove(channel); - } - - return interceptor; - } catch (IllegalArgumentException e) { - // Try again - return (PacketInterceptor) channel.pipeline().get(handlerName); - } - } - - public Channel getChannel(Player player) { - 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 channel; - } - - public void uninjectPlayer(Player player) { - uninjectChannel(getChannel(player)); - } - - 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(() -> { - try { - channel.pipeline().remove(handlerName); - } catch (NoSuchElementException e) { - // ignore - } - }); - } - - public boolean hasInjected(Player player) { - return hasInjected(getChannel(player)); - } - - public boolean hasInjected(Channel channel) { - return channel.pipeline().get(handlerName) != null; + getInterceptor(player).ifPresent(i -> i.receivePacket(packet)); } public final void close() { - if (!closed) { - closed = true; + if(closed) + return; + closed = true; - // Remove our handlers - for (Player player : plugin.getServer().getOnlinePlayers()) { - uninjectPlayer(player); - } + HandlerList.unregisterAll(this); - // Clean up Bukkit - HandlerList.unregisterAll(listener); - unregisterChannelHandler(); + for (Player player : plugin.getServer().getOnlinePlayers()) { + getInterceptor(player).ifPresent(PacketInterceptor::close); } } - @Deprecated - public interface PacketFilter { - Object onPacket(Player player, Channel channel, Object packet); + private Optional getInterceptor(Player player) { + synchronized (playerInterceptors) { + return Optional.ofNullable(playerInterceptors.get(player)); + } } + private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); + private static final Class playerConnection = Reflection.getUntypedClass("{nms.server.network}.PlayerConnection"); + private static final FieldAccessor getConnection = Reflection.getField("{nms.server.level}.EntityPlayer", playerConnection, 0); + private static final Class networkManager = Reflection.getUntypedClass("{nms.network}.NetworkManager"); + private static final FieldAccessor getManager = Reflection.getField(playerConnection, networkManager, 0); + private static final FieldAccessor getChannel = Reflection.getField(networkManager, Channel.class, 0); + private final class PacketInterceptor extends ChannelDuplexHandler { - // Updated by the login event - public volatile Player player; + private final Player player; + private final Channel channel; + + private PacketInterceptor(Player player) { + this.player = player; + + Object connection = getConnection.get(getPlayerHandle.invoke(player)); + Object manager = getManager.get(connection); + channel = getChannel.get(manager); + + synchronized (playerInterceptors) { + playerInterceptors.put(player, this); + } + + channel.pipeline().addBefore("packet_handler", handlerName, this); + } + + private void sendPacket(Object packet) { + channel.pipeline().writeAndFlush(packet); + } + + private void receivePacket(Object packet) { + channel.pipeline().context("encoder").fireChannelRead(packet); + } + + private void close() { + if(channel.isActive()) { + channel.eventLoop().execute(() -> { + try { + channel.pipeline().remove(handlerName); + } catch (NoSuchElementException e) { + // ignore + } + }); + } + + synchronized (playerInterceptors) { + playerInterceptors.remove(player, this); + } + } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - // Intercept channel - final Channel channel = ctx.channel(); - handleLoginStart(channel, msg); - try { - msg = onPacketInAsync(player, channel, msg); + msg = filterPacket(player, msg); } catch (Exception e) { - plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e); + plugin.getLogger().log(Level.SEVERE, "Error during incoming packet processing", e); } if (msg != null) { @@ -421,9 +168,9 @@ public class TinyProtocol { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { try { - msg = onPacketOutAsync(player, ctx.channel(), msg); + msg = filterPacket(player, msg); } catch (Exception e) { - plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e); + plugin.getLogger().log(Level.SEVERE, "Error during outgoing packet processing", e); } if (msg != null) { @@ -431,10 +178,17 @@ public class TinyProtocol { } } - private void handleLoginStart(Channel channel, Object packet) { - if (PACKET_LOGIN_IN_START.isInstance(packet)) { - channelLookup.put(ChatWrapper.impl.getNameByLoginPacket(packet), channel); + private Object filterPacket(Player player, Object packet) { + List> filters = packetFilters.getOrDefault(packet.getClass(), Collections.emptyList()); + + for(BiFunction filter : filters) { + packet = filter.apply(player, packet); + + if(packet == null) + break; } + + return packet; } } } diff --git a/SpigotCore_Main/src/de/steamwar/core/ChatWrapper.java b/SpigotCore_Main/src/de/steamwar/core/ChatWrapper.java index eeddd1c..2f4df8f 100644 --- a/SpigotCore_Main/src/de/steamwar/core/ChatWrapper.java +++ b/SpigotCore_Main/src/de/steamwar/core/ChatWrapper.java @@ -23,5 +23,4 @@ public interface ChatWrapper { ChatWrapper impl = VersionDependent.getVersionImpl(Core.getInstance()); Object stringToChatComponent(String text); - String getNameByLoginPacket(Object packet); } diff --git a/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index 1fc9e31..88a5cf9 100644 --- a/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java @@ -37,7 +37,7 @@ public class ProtocolUtils { private ProtocolUtils() {} public static void broadcastPacket(Object packet) { - Bukkit.getOnlinePlayers().stream().map(TinyProtocol.instance::getChannel).filter(TinyProtocol.instance::hasInjected).forEach(channel -> TinyProtocol.instance.sendPacket(channel, packet)); + Bukkit.getOnlinePlayers().forEach(player -> TinyProtocol.instance.sendPacket(player, packet)); } public static BiFunction, Object> arrayCloneGenerator(Class elementClass) {