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/Reflection.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java index f1138fc..7348bcf 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java @@ -4,9 +4,7 @@ import de.steamwar.core.Core; import jdk.internal.misc.Unsafe; import org.bukkit.Bukkit; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; +import java.lang.reflect.*; import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -135,15 +133,16 @@ public final class Reflection { return getField(getClass(className), fieldType, index); } - // Common method - private static FieldAccessor getField(Class target, String name, Class fieldType, int index) { + public static FieldAccessor getField(Class target, Class fieldType, int index, Class... parameters) { + return getField(target, null, fieldType, index, parameters); + } + + private static FieldAccessor getField(Class target, String name, Class fieldType, int index, Class... parameters) { for (final Field field : target.getDeclaredFields()) { - if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) { + if(matching(field, name, fieldType, parameters) && index-- <= 0) { field.setAccessible(true); - // A function for retrieving a specific field value return new FieldAccessor() { - @Override @SuppressWarnings("unchecked") public T get(Object target) { @@ -179,6 +178,24 @@ public final class Reflection { throw new IllegalArgumentException("Cannot find field with type " + fieldType); } + private static boolean matching(Field field, String name, Class fieldType, Class... parameters) { + if(name != null && !field.getName().equals(name)) + return false; + + if(!fieldType.isAssignableFrom(field.getType())) + return false; + + if(parameters.length > 0) { + Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments(); + + for(int i = 0; i < parameters.length; i++) { + if(arguments[i] != parameters[i]) + return false; + } + } + return true; + } + /** * Search for the first publicly and privately defined method of the given name and parameter count. * diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 82a4afb..b1bbb00 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -1,253 +1,81 @@ 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); +public class TinyProtocol implements Listener { - // 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; + private static final Class craftServer = Reflection.getClass("{obc}.CraftServer"); + private static final Class dedicatedPlayerList = Reflection.getClass("{nms.server.dedicated}.DedicatedPlayerList"); + private static final FieldAccessor getPlayerList = Reflection.getField(craftServer, dedicatedPlayerList, 0); + private static final Class playerList = Reflection.getClass("{nms.server.players}.PlayerList"); + private static final Class minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); + private static final FieldAccessor getMinecraftServer = Reflection.getField(playerList, minecraftServer, 0); + private static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); + private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServer, serverConnection, 0); + private static final Class networkManager = Reflection.getClass("{nms.network}.NetworkManager"); + private static final FieldAccessor getConnections = Reflection.getField(serverConnection, List.class, 0, networkManager); 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 final List connections; + private boolean closed; + + private final Map, List>> packetFilters = new HashMap<>(); + private final Map playerInterceptors = new HashMap<>(); + + private TinyProtocol(final Plugin plugin) { this.plugin = plugin; + this.handlerName = "tiny-" + plugin.getName() + "-" + ++id; + this.connections = getConnections.get(getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer())))); - // Compute handler name - this.handlerName = "tiny-" + plugin.getName() + "-" + ID.incrementAndGet(); + plugin.getServer().getPluginManager().registerEvents(this, plugin); - // Prepare existing players - listener = new Listener() { - - @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 +87,84 @@ 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 FieldAccessor getChannel = Reflection.getField(networkManager, Channel.class, 0); + private static final FieldAccessor getUUID = Reflection.getField(networkManager, UUID.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; + + channel = getChannel.get(connections.stream().filter(connection -> player.getUniqueId().equals(getUUID.get(connection))).findAny().orElseThrow(() -> { + player.kickPlayer("An injection failure happend."); + return new SecurityException("Could not find channel for player " + player.getName()); + })); + + 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 +175,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 +185,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) {