package lloyd.core import java.util.Collections import java.util.UUID import java.util.concurrent.atomic.AtomicInteger import java.util.logging.Level import java.util.Arrays import java.util.regex.Matcher import java.util.regex.Pattern import java.lang.Class.forName import org.bukkit.Bukkit import org.bukkit.ChatColor import org.bukkit.entity.Player import org.bukkit.plugin.Plugin import org.bukkit.scheduler.BukkitRunnable import com.google.common.collect.Lists import com.google.common.collect.MapMaker import com.mojang.authlib.GameProfile import io.netty.channel.Channel import io.netty.channel.ChannelDuplexHandler import io.netty.channel.ChannelFuture import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelInboundHandlerAdapter import io.netty.channel.ChannelInitializer import io.netty.channel.ChannelPromise import lloyd.core.MOTDManager.Companion.Reflection.getField @Suppress("UNCHECKED_CAST") class MOTDManager(plugin: Plugin) { init { TinyProtocol(plugin) } class TinyProtocol(p: Plugin) { var initPlugin = p var serverInfoClass = getNMSClass("PacketStatusOutServerInfo") var cs = getNMSClass("ChatSerializer") var serverPingClass = getNMSClass("ServerPing") as Class var getMotdClass = getNMSClass("IChatBaseComponent") as Class var setMotdClass = getNMSClass("ChatComponentText") as Class var getServerClass = getNMSClass("ServerData") as Class var getplayerClass = getNMSClass("ServerPingPlayerSample") as Class var serverPing = getField(serverInfoClass!!, serverPingClass, 0) var player = getField(serverPingClass, getplayerClass, 0) var motdClass = getField(serverPingClass, getMotdClass, 0) var serverClass = getField(serverPingClass, getServerClass, 0) var motdInvoker: Reflection.ConstructorInvoker = Reflection.getConstructor(setMotdClass, String::class.java) var serverInvoker: Reflection.ConstructorInvoker = Reflection.getConstructor(getServerClass, String::class.java, Int::class.java) private val ID = AtomicInteger(0) private val minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer") private val serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection") private val getMinecraftServer = getField("{obc}.CraftServer", minecraftServerClass, 0) private val getServerConnection = getField(minecraftServerClass, serverConnectionClass, 0) private val getNetworkMarkers: Reflection.MethodInvoker = Reflection.getTypedMethod(serverConnectionClass, null, List::class.java, serverConnectionClass) val PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart") val getGameProfile = getField(PACKET_LOGIN_IN_START, GameProfile::class.java, 0) var channelLookup = MapMaker().weakValues().makeMap() var uninjectedChannels = Collections.newSetFromMap(MapMaker().weakKeys().makeMap()) var networkManagers: MutableList? = null var serverChannels = Lists.newArrayList() var serverChannelHandler: ChannelInboundHandlerAdapter? = null var beginInitProtocol: ChannelInitializer? = null var endInitProtocol: ChannelInitializer? = null var handlerName: String? = null @Volatile protected var closed: Boolean = false var plugin: Plugin? = null init{ this.plugin = initPlugin this.handlerName = getHandlerNameM() try { registerChannelHandler() } catch (ex: IllegalArgumentException) { object : BukkitRunnable() { override fun run() { registerChannelHandler() } }.runTask(plugin) } } fun createServerChannelHandler() { endInitProtocol = object : ChannelInitializer() { @Throws(Exception::class) override fun initChannel(channel: Channel) = try{ synchronized(this@TinyProtocol.networkManagers!!){ if (!closed) { channel.eventLoop().submit(Runnable() { injectChannelInternal(channel) }) } } } catch(ex: Exception){ } } beginInitProtocol = object : ChannelInitializer() { @Throws(Exception::class) override fun initChannel(channel: Channel) { channel.pipeline().addLast(endInitProtocol) } } serverChannelHandler = object : ChannelInboundHandlerAdapter() { @Throws(Exception::class) override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { var channel = msg as Channel channel.pipeline().addFirst(beginInitProtocol) ctx.fireChannelRead(msg) } } } fun registerChannelHandler() { var mcServer = getMinecraftServer[Bukkit.getServer()] var serverConnection = getServerConnection[mcServer] var looking = true networkManagers = (getNetworkMarkers.invoke(0, serverConnection) as List).toMutableList() createServerChannelHandler() var i = 0 while (looking) { var list = Reflection.getField(serverConnection.javaClass, List::class.java, i)[serverConnection] for (item in list) { if (!ChannelFuture::class.java.isInstance(item)) break var serverChannel = (item as ChannelFuture).channel() serverChannels.add(serverChannel) serverChannel.pipeline().addFirst(serverChannelHandler) looking = false } i++ } } fun onPacketOutAsync(reciever: Player?, channel: Channel, packet: Any): Any { if (serverInfoClass!!.isInstance(packet)) { var ping = serverPing.get(packet) var playersample = player.get(ping) var serverdata = serverClass.get(ping) var address = channel.remoteAddress().toString() var baseMaxPlayers = Reflection.getMethod(playersample::class.java, "a").invoke(playersample) as Int var baseOnlinePlayers = Reflection.getMethod(playersample::class.java, "b").invoke(playersample) as Int var baseHoverList = Reflection.getMethod(playersample::class.java, "c").invoke(playersample) as Array var baseMOTD = Reflection.getMethod(cs!!, "a", getMotdClass).invoke(cs!!, Reflection.getMethod(ping::class.java, "a").invoke(ping)) as String baseMOTD = baseMOTD.subSequence(9..baseMOTD.length - 2) as String var baseVersion = Reflection.getMethod(serverdata::class.java, "a").invoke(serverdata) as String //address - adres gracza (String) //baseMOTD - domyślne motd (String) //baseHoverList - domyślny hover (Array, można użyć metody getHover(MutableList)) //baseOnlinePlayers / baseMaxPlayers - domyślna liczba gracza i sloty (Int/Int) //baseVersion - wersja serwera (String) //version - customowa wersja (przy ustawianiu customowej wersji tą wartość powinno się zmienić) (String) //jescze cancelEvent jest który wyświetla czerwone Can't connect to server (Boolean) var event = ServerPingEvent(address, baseMOTD, baseHoverList, baseOnlinePlayers, baseMaxPlayers, baseVersion, null) Bukkit.getServer().pluginManager.callEvent(event) if(event.isCancelled) return Object(); if(event.max != null) Reflection.getField(playersample::class.java, "a", Int::class.java).set(playersample, event.max) if(event.online != null) Reflection.getField(playersample::class.java, "b", Int::class.java).set(playersample, event.online) if(event.hover != null) Reflection.getField(playersample::class.java, "c", Array::class.java).set(playersample, getHover(event.hover!!.toMutableList())) if(event.motd != null) motdClass.set(ping, motdInvoker.invoke(color(event.motd!!))) if(event.version != null) serverClass.set(ping, serverInvoker.invoke(color(event.version!!), 0)) } return packet } fun getHandlerNameM(): String { return "tiny-${this.plugin!!.name}-${ID.incrementAndGet()}" } private fun injectChannelInternal(channel: Channel): PacketInterceptor { try { var interceptor: PacketInterceptor? interceptor = PacketInterceptor() channel.pipeline().addBefore("packet_handler", handlerName, interceptor) uninjectedChannels.remove(channel) return interceptor } catch (e: IllegalArgumentException) { return channel.pipeline().get(handlerName) as PacketInterceptor } } private inner class PacketInterceptor() : ChannelDuplexHandler() { @Volatile var player: Player? = null override fun write(ctx: ChannelHandlerContext, msg: Any?, promise: ChannelPromise) { var msg = msg try { if (serverInfoClass!!.isInstance(msg)) { msg = onPacketOutAsync(player, ctx.channel(), msg!!) } } catch (e: Exception) { plugin!!.logger.log(Level.SEVERE, "Error in onPacketOutAsync().", e) } if (msg != null) { super.write(ctx, msg, promise) } } private fun handleLoginStart(channel: Channel, packet: Any?) { if (PACKET_LOGIN_IN_START.isInstance(packet)) { var profile = getGameProfile.get(packet!!) channelLookup[profile.name] = channel } } } private fun color(str: String): String { return ChatColor.translateAlternateColorCodes('&', str) } private fun getHover(list: MutableList): Array { if (list == null || list.size == 0) return arrayOf(GameProfile(UUID(0, 0), "")) var ret = mutableListOf() for(line in list){ if(line == null){ ret.add(GameProfile(UUID(0, 0), "")) } else{ ret.add(GameProfile(UUID(0, 0), color(line))) } } return ret.toTypedArray() } private fun getNMSClass(name: String): Class<*>? { var nms = Bukkit.getServer().javaClass.getPackage().name nms = "net.minecraft.server." + nms.substring(nms.lastIndexOf('.') + 1) try { if (name == "ServerData") { return forName("$nms.ServerPing\$ServerData") } else if (name == "ServerPingPlayerSample") { return forName("$nms.ServerPing\$ServerPingPlayerSample") } else if (name == "PacketStatusOutServerInfo") { return forName("$nms.PacketStatusOutServerInfo") } else if (name == "ServerPing") { return forName("$nms.ServerPing") } else if (name == "IChatBaseComponent") { return forName("$nms.IChatBaseComponent") } else if (name == "ChatComponentText") { return forName("$nms.ChatComponentText") } else if (name == "ChatSerializer") { return forName("$nms.IChatBaseComponent\$ChatSerializer") } } catch (ex: ClassNotFoundException) { plugin!!.logger.info("MOTDManager: class $name not found") } return null } } companion object { object Reflection { interface ConstructorInvoker { operator fun invoke(vararg arguments: Any): Any } interface MethodInvoker { operator fun invoke(target: Any, vararg arguments: Any): Any } interface FieldAccessor { operator fun get(target: Any): T operator fun set(target: Any, value: Any) fun hasField(target: Any): Boolean } private val OBC_PREFIX = Bukkit.getServer().javaClass.getPackage().name private val NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server") private val VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", "") private val MATCH_VARIABLE = Pattern.compile("\\{([^}]+)\\}") fun getField(target: Class<*>, name: String, fieldType: Class): FieldAccessor { return getField(target, name, fieldType, 0) } fun getField(className: String, name: String, fieldType: Class): FieldAccessor { return getField(getClass(className), name, fieldType, 0) } fun getField(target: Class<*>, fieldType: Class, index: Int): FieldAccessor { return getField(target, null, fieldType, index) } fun getField(className: String, fieldType: Class, index: Int): FieldAccessor { return getField(getClass(className), fieldType, index) } fun getField(target: Class<*>, name: String?, fieldType: Class, index: Int): FieldAccessor { var index = index for (field in target.declaredFields) { if ((name == null || field.name.equals(name)) && fieldType.isAssignableFrom(field.type) && index-- <= 0) { field.isAccessible = true return object : FieldAccessor { override fun get(target: Any): T { try { return field.get(target) as T } catch (e: IllegalAccessException) { throw RuntimeException("Cannot access reflection.", e) } } override fun set(target: Any, value: Any) { try { field.set(target, value) } catch (e: IllegalAccessException) { throw RuntimeException("Cannot access reflection.", e) } } override fun hasField(target: Any): Boolean { return field.declaringClass.isAssignableFrom(target.javaClass) } } } } if (target.superclass != null) return getField(target.superclass, name, fieldType, index) throw IllegalArgumentException("Cannot find field with type $fieldType") } fun getMethod(clazz: Class<*>, methodName: String?, vararg params: Class<*>): MethodInvoker { return getTypedMethod(clazz, methodName, null, *params) } fun getTypedMethod(clazz: Class<*>, methodName: String?, returnType: Class<*>?, vararg params: Class<*>): MethodInvoker { for (method in clazz.declaredMethods) { if ((methodName == null || method.name.equals(methodName)) && (returnType == null || method.returnType.equals(returnType)) && Arrays.equals(method.parameterTypes, params)) { method.isAccessible = true return object : MethodInvoker { override fun invoke(target: Any, vararg arguments: Any): Any { try { return method.invoke(target, *arguments) } catch (e: Exception) { throw RuntimeException("Cannot invoke method $method cuz ${e.message} and ${e.toString()}", e) } finally { } } } } } if (clazz.superclass != null) return getMethod(clazz.superclass, methodName, *params) throw IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))) } fun getConstructor(clazz: Class<*>, vararg params: Class<*>): ConstructorInvoker { for (constructor in clazz.declaredConstructors) { if (Arrays.equals(constructor.parameterTypes, params)) { constructor.isAccessible = true return object : ConstructorInvoker { override fun invoke(vararg arguments: Any): Any { try { return constructor.newInstance(*arguments) } catch (e: Exception) { throw RuntimeException("Cannot invoke constructor $constructor", e) } } } } } throw IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))) } fun getUntypedClass(lookupName: String): Class { return getClass(lookupName) as Class } fun getClass(lookupName: String): Class<*> { return getCanonicalClass(expandVariables(lookupName)) } fun getMinecraftClass(name: String): Class<*> { return getCanonicalClass("$NMS_PREFIX.$name") } private fun getCanonicalClass(canonicalName: String): Class<*> { try { return forName(canonicalName) } catch (e: ClassNotFoundException) { throw IllegalArgumentException("Cannot find $canonicalName", e) } } private fun expandVariables(name: String): String { val output = StringBuffer() val matcher = MATCH_VARIABLE.matcher(name) while (matcher.find()) { val variable = matcher.group(1) var replacement = "" if ("nms".equals(variable, ignoreCase = true)) replacement = NMS_PREFIX else if ("obc".equals(variable, ignoreCase = true)) replacement = OBC_PREFIX else if ("version".equals(variable, ignoreCase = true)) replacement = VERSION else throw IllegalArgumentException("Unknown variable: $variable") if (replacement.length > 0 && matcher.end() < name.length && name[matcher.end()] != '.') replacement += "." matcher.appendReplacement(output, Matcher.quoteReplacement(replacement)) } matcher.appendTail(output) return output.toString() } } } }