package org.henrya.pingapi.v1_13_R1; import io.netty.channel.Channel; import net.minecraft.server.v1_13_R1.MinecraftServer; import net.minecraft.server.v1_13_R1.NetworkManager; import net.minecraft.server.v1_13_R1.ServerConnection; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_13_R1.CraftServer; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.server.ServerListPingEvent; import org.henrya.pingapi.reflect.ReflectUtils; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; /** * A class that injects out packet listener into open NetworkManagers * @author Henry Anderson */ public class PingInjector implements Listener { private MinecraftServer server; private List<?> networkManagers; /** * Constructs a new PingInjector and gets the list of open NetworkManager instances */ public PingInjector() { try { CraftServer craftserver = (CraftServer) Bukkit.getServer(); Field console = craftserver.getClass().getDeclaredField("console"); console.setAccessible(true); this.server = (MinecraftServer) console.get(craftserver); ServerConnection conn = this.server.getServerConnection(); networkManagers = Collections.synchronizedList((List<?>) this.getNetworkManagerList(conn)); } catch(IllegalAccessException | NoSuchFieldException e) { e.printStackTrace(); } } /** * Iterates through every open NetworkManager and adds my ChannelDuplexHandler subclass into the pipeline * This allows you to listen for outgoing packets and modify them before they are sent * * The List of NetworkManager instances is converted to an array to avoid ConcurrentModificationExceptions * NullPointerExceptions, IllegalArgumentExceptions, and NoSuchElementException only occur if there is a massive amount of ping requests being sent to the server. * NullPointerExceptions are thrown when the pipeline has yet to be created. * Since ping responses are handled on separate threads IllegalArgumentExceptions are thrown when this method is invoked at the same time on two different threads * This means the null check will be passed and this method will attempt to create a duplicate handler which throws this exception * NoSuchElementExceptions have a similar cause. They are caused when the "packet_handler" has yet to be added. * The best solution I could find is simply ignoring these exceptions */ public void injectOpenConnections() { try { Field field = ReflectUtils.getFirstFieldByType(NetworkManager.class, Channel.class); field.setAccessible(true); for(Object manager : networkManagers.toArray()) { Channel channel = (Channel) field.get(manager); if(channel.pipeline().context("pingapi_handler") == null && (channel.pipeline().context("packet_handler") != null)) { channel.pipeline().addBefore("packet_handler", "pingapi_handler", new DuplexHandler()); } } } catch(IllegalAccessException e) { e.printStackTrace(); } catch(NullPointerException | IllegalArgumentException | NoSuchElementException ignored) {} } /** * Returns the list of open NetworkManager instances * @param conn The ServerConnection instance * @return A List of NetworkManager instances downcasted to an Object */ public Object getNetworkManagerList(ServerConnection conn) { try { for(Method method : conn.getClass().getDeclaredMethods()) { method.setAccessible(true); if(method.getReturnType() == List.class) { Object object = method.invoke(null, conn); return object; } } } catch(IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return null; } /** * Injects a DuplexHandler into each NetworkManager's pipeline when the server receives a ping packet * @param event The event */ @EventHandler public void serverListPing(ServerListPingEvent event) { this.injectOpenConnections(); } }