diff --git a/README.md b/README.md index d8dbfbb..eb322bb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ How to use: server.example.com._p25565._v1_12_2._ofalse._uBACKUSERNAME.viaaas.ex Parts: - ```server.example.com```: backend server address - ```_p```: backend port -- ```_v```: backend version (protocol id https://wiki.vg/Protocol_version_numbers or name with underline instead of dots). Current default is 1.8, may change to auto detection in the future +- ```_v```: backend version (protocol id https://wiki.vg/Protocol_version_numbers or name with underline instead of dots). ```AUTO``` is default and 1.8 is fallback if it fails. - ```_o```: ```t``` to force online mode in frontend, ```f``` to disable online mode in frontend. If not set, it will be based on backend online mode. - ```_u```: username to use in backend connection - ```viaaas.example.com```: hostname suffix (defined in config) @@ -36,7 +36,7 @@ How to start VIAaaS server: - ```java -jar VIAaaS-all.jar``` Usage for offline mode: -- Connect to ```mc.example.com._v1_8.viaaas.localhost``` +- Connect to ```mc.example.com.viaaas.localhost``` Usage for online mode: - You can use two accounts (avoids Bad Login error), the same account for front-end and back-end connections or use ```_of``` @@ -48,7 +48,7 @@ Usage for online mode: - Go to VIAaaS auth webpage (https://localhost:25543/), configure the CORS Proxy URL (something like http://localhost:8080/, note the ending slash) and listen to the username A that you're using to connect to the proxy. - Add the account B you'll use in ```_u(account B)``` parameter to browser auth page. -- Connect to ```mc.example.com._v1_8._u(account B).viaaas.localhost``` (```_u``` parameter can be removed if you are using the same username) +- Connect to ```mc.example.com._u(account B).viaaas.localhost``` (```_u``` parameter can be removed if you are using the same username) - Approve the login in auth webpage - If you use the same online mode account, your client will give Bad Login after you approve it in your browser. You can use https://www.curseforge.com/minecraft/mc-mods/auth-me for reauthenticating the client. diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/Util.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/Util.kt index 157a989..29a3130 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/Util.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/Util.kt @@ -3,6 +3,7 @@ package com.github.creeper123123321.viaaas import com.github.creeper123123321.viaaas.config.VIAaaSConfig import com.google.common.base.Preconditions import com.google.common.primitives.Ints +import io.netty.buffer.ByteBuf import io.netty.channel.Channel import io.netty.handler.codec.DecoderException import org.slf4j.LoggerFactory @@ -132,4 +133,6 @@ fun writeFlushClose(ch: Channel, obj: Any) { ch.writeAndFlush(obj).addListener { ch.close() } } -val secureRandom = if (VIAaaSConfig.useStrongRandom) SecureRandom.getInstanceStrong() else SecureRandom() \ No newline at end of file +val secureRandom = if (VIAaaSConfig.useStrongRandom) SecureRandom.getInstanceStrong() else SecureRandom() + +fun readableToByteArray(byteBuf: ByteBuf) = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) } \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaS.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaS.kt index 10c3b84..752e902 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaS.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaS.kt @@ -7,6 +7,7 @@ import com.github.creeper123123321.viaaas.handler.FrontEndInit import com.github.creeper123123321.viaaas.platform.* import com.github.creeper123123321.viaaas.web.ViaWebApp import com.github.creeper123123321.viaaas.web.WebDashboardServer +import com.google.gson.JsonParser import de.gerrygames.viarewind.api.ViaRewindConfigImpl import io.ktor.application.* import io.ktor.client.* @@ -36,17 +37,15 @@ import io.netty.util.concurrent.Future import us.myles.ViaVersion.ViaManager import us.myles.ViaVersion.api.Via import us.myles.ViaVersion.api.data.MappingDataLoader -import us.myles.ViaVersion.util.GsonUtil -import us.myles.viaversion.libs.gson.JsonObject +import us.myles.ViaVersion.api.protocol.ProtocolVersion import java.io.File import java.net.InetAddress import java.security.KeyPairGenerator import java.util.concurrent.CompletableFuture -val viaaasVer = GsonUtil.getGson().fromJson( - AspirinPlatform::class.java.classLoader.getResourceAsStream("viaaas_info.json")!!.reader(Charsets.UTF_8).readText(), - JsonObject::class.java -).get("version").asString +val viaaasVer = JsonParser.parseString( + AspirinPlatform::class.java.classLoader.getResourceAsStream("viaaas_info.json")!!.reader(Charsets.UTF_8).readText() +).asJsonObject.get("version").asString val viaWebServer = WebDashboardServer() var runningServer = true val httpClient = HttpClient { @@ -89,6 +88,9 @@ fun channelSocketFactory(): ChannelFactory { return ChannelFactory { NioSocketChannel() } } +val parentLoop = eventLoopGroup() +val childLoop = eventLoopGroup() + fun main(args: Array) { // Stolen from https://github.com/VelocityPowered/Velocity/blob/dev/1.1.0/proxy/src/main/java/com/velocitypowered/proxy/Velocity.java if (System.getProperty("io.netty.allocator.maxOrder") == null) { @@ -109,14 +111,12 @@ fun main(args: Array) { ) MappingDataLoader.enableMappingsCache() Via.getManager().init() + ProtocolVersion.register(-2, "AUTO") AspirinRewind.init(ViaRewindConfigImpl(File("config/viarewind.yml"))) AspirinBackwards.init(File("config/viabackwards")) - val parent = eventLoopGroup() - val child = eventLoopGroup() - val future = ServerBootstrap() - .group(parent, child) + .group(parentLoop, childLoop) .channelFactory(channelServerSocketFactory()) .childHandler(FrontEndInit) .childOption(ChannelOption.IP_TOS, 0x18) @@ -138,7 +138,7 @@ fun main(args: Array) { ktorServer?.stop(1000, 1000) httpClient.close() - listOf>(future.channel().close(), parent.shutdownGracefully(), child.shutdownGracefully()) + listOf>(future.channel().close(), parentLoop.shutdownGracefully(), childLoop.shutdownGracefully()) .forEach { it.sync() } Via.getManager().destroy() diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaSAddress.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaSAddress.kt index 41f01b0..506db0d 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaSAddress.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/VIAaaSAddress.kt @@ -32,7 +32,6 @@ class VIAaaSAddress { }.asReversed().joinToString(".") viaOptions = optionsList.asReversed().joinToString(".") - viaSuffix = viaHostName return this @@ -58,6 +57,9 @@ class VIAaaSAddress { protocol = it.version } } + if (protocol == -2) { + protocol = null + } } part.startsWith("_u", ignoreCase = true) -> { if (arg.length > 16) throw IllegalArgumentException("Invalid username") diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/codec/MinecraftCodec.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/codec/MinecraftCodec.kt index e2b1ca2..321417f 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/codec/MinecraftCodec.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/codec/MinecraftCodec.kt @@ -14,7 +14,7 @@ class MinecraftCodec : MessageToMessageCodec() { val buf = ByteBufAllocator.DEFAULT.buffer() try { val handler = ctx.pipeline().get(MinecraftHandler::class.java) - PacketRegistry.encode(msg, buf, handler.data.frontVer!!) + PacketRegistry.encode(msg, buf, handler.data.frontVer!!, serverBound = !handler.frontEnd) out.add(buf.retain()) } finally { buf.release() diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/config/VIAaaSConfig.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/config/VIAaaSConfig.kt index 307f031..7ed4e83 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/config/VIAaaSConfig.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/config/VIAaaSConfig.kt @@ -35,4 +35,6 @@ object VIAaaSConfig : Config(File("config/viaaas.yml")) { emptyList() )!!.map { it as String } val forceOnlineMode: Boolean get() = this.getBoolean("force-online-mode", false) + val showVersionPing: Boolean get() = this.getBoolean("show-version-ping", true) + val showBrandInfo: Boolean get() = this.getBoolean("show-brand-info", true) } \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/BackEndInit.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/BackEndInit.kt index 28a43a9..70813f1 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/BackEndInit.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/BackEndInit.kt @@ -2,6 +2,7 @@ package com.github.creeper123123321.viaaas.handler import com.github.creeper123123321.viaaas.codec.FrameCodec import com.github.creeper123123321.viaaas.codec.MinecraftCodec +import com.github.creeper123123321.viaaas.handler.autoprotocol.ProtocolDetectorHandler import io.netty.channel.Channel import io.netty.channel.ChannelInitializer import io.netty.handler.timeout.ReadTimeoutHandler @@ -19,6 +20,11 @@ class BackEndInit(val connectionData: ConnectionData) : ChannelInitializer() { // "compress" .addLast("flow-handler", FlowControlHandler()) .addLast("mc", MinecraftCodec()) - .addLast( - "handler", MinecraftHandler( - ConnectionData(frontChannel = ch), frontEnd = true - ) - ) + .addLast("handler", MinecraftHandler(ConnectionData(frontChannel = ch), frontEnd = true)) } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/Util.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/Util.kt index c2e029e..849ab06 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/Util.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/Util.kt @@ -2,8 +2,10 @@ package com.github.creeper123123321.viaaas.handler import com.github.creeper123123321.viaaas.packet.Packet import com.github.creeper123123321.viaaas.send - +import us.myles.ViaVersion.api.protocol.ProtocolVersion fun forward(handler: MinecraftHandler, packet: Packet, flush: Boolean = false) { send(handler.other!!, packet, flush) -} \ No newline at end of file +} + +fun is1_7(handler: MinecraftHandler) = handler.data.frontVer!! <= ProtocolVersion.v1_7_6.version \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetectionState.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetectionState.kt new file mode 100644 index 0000000..ebdf743 --- /dev/null +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetectionState.kt @@ -0,0 +1,37 @@ +package com.github.creeper123123321.viaaas.handler.autoprotocol + +import com.github.creeper123123321.viaaas.handler.MinecraftHandler +import com.github.creeper123123321.viaaas.handler.state.MinecraftConnectionState +import com.github.creeper123123321.viaaas.mcLogger +import com.github.creeper123123321.viaaas.packet.Packet +import com.github.creeper123123321.viaaas.packet.status.StatusResponse +import com.google.gson.JsonParser +import io.netty.channel.ChannelHandlerContext +import us.myles.ViaVersion.api.protocol.ProtocolVersion +import us.myles.ViaVersion.packets.State +import java.nio.channels.ClosedChannelException +import java.util.concurrent.CompletableFuture + +class ProtocolDetectionState(val future: CompletableFuture) : MinecraftConnectionState { + override val state: State + get() = State.STATUS + + override fun handlePacket(handler: MinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) { + handler.data.frontChannel.close() + if (packet !is StatusResponse) throw IllegalArgumentException() + val ver = ProtocolVersion.getProtocol( + JsonParser.parseString(packet.json).asJsonObject.getAsJsonObject("version").get("protocol").asInt + ) + future.complete(ver) + mcLogger.info("Auto-detected $ver for ${handler.remoteAddress}") + } + + override fun disconnect(handler: MinecraftHandler, msg: String) { + super.disconnect(handler, msg) + handler.data.frontChannel.close() + } + + override fun onInactivated(handler: MinecraftHandler) { + future.completeExceptionally(ClosedChannelException()) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetector.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetector.kt new file mode 100644 index 0000000..9420a60 --- /dev/null +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetector.kt @@ -0,0 +1,77 @@ +package com.github.creeper123123321.viaaas.handler.autoprotocol + +import com.github.creeper123123321.viaaas.childLoop +import com.github.creeper123123321.viaaas.codec.FrameCodec +import com.github.creeper123123321.viaaas.codec.MinecraftCodec +import com.github.creeper123123321.viaaas.handler.ConnectionData +import com.github.creeper123123321.viaaas.handler.MinecraftHandler +import com.github.creeper123123321.viaaas.packet.handshake.Handshake +import com.github.creeper123123321.viaaas.mcLogger +import com.github.creeper123123321.viaaas.packet.status.StatusRequest +import com.github.creeper123123321.viaaas.send +import java.util.concurrent.CompletableFuture +import java.net.InetSocketAddress +import us.myles.ViaVersion.api.protocol.ProtocolVersion +import io.netty.handler.timeout.ReadTimeoutHandler +import io.netty.channel.socket.nio.NioSocketChannel +import com.google.common.cache.CacheLoader +import java.util.concurrent.TimeUnit +import com.google.common.cache.CacheBuilder +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.util.concurrent.Future +import us.myles.ViaVersion.packets.State +import java.util.concurrent.ExecutionException + +object ProtocolDetector { + private val SERVER_VER = CacheBuilder.newBuilder() + .expireAfterAccess(100, TimeUnit.SECONDS) + .build(CacheLoader.from { address: InetSocketAddress? -> + val future = CompletableFuture() + try { + val ch: ChannelFuture = Bootstrap() + .group(childLoop) + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.IP_TOS, 0x18) + .handler(object : ChannelInitializer() { + override fun initChannel(channel: Channel) { + val data = ConnectionData(channel, state = ProtocolDetectionState(future), frontVer = -1) + channel.pipeline() + .addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS)) + .addLast("frame", FrameCodec()) + .addLast("mc", MinecraftCodec()) + .addLast("handler", MinecraftHandler(data, frontEnd = false)) + } + }) + .connect(address!!) + ch.addListener { future1: Future -> + if (!future1.isSuccess) { + future.completeExceptionally(future1.cause()) + } else { + ch.channel().eventLoop().execute { + val handshake = Handshake() + handshake.address = address.hostString + handshake.port = address.port + handshake.protocolId = -1 + handshake.nextState = State.STATUS + send(ch.channel(), handshake) + send(ch.channel(), StatusRequest(), flush = true) + } + } + } + } catch (throwable: Throwable) { + future.completeExceptionally(throwable) + } + future + }) + + fun detectVersion(address: InetSocketAddress): CompletableFuture { + return try { + SERVER_VER[address] + } catch (e: ExecutionException) { + mcLogger.warn("Protocol auto detector error: ", e) + CompletableFuture.completedFuture(null) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetectorHandler.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetectorHandler.kt new file mode 100644 index 0000000..9e38bf5 --- /dev/null +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/autoprotocol/ProtocolDetectorHandler.kt @@ -0,0 +1,90 @@ +package com.github.creeper123123321.viaaas.handler.autoprotocol + +import com.github.creeper123123321.viaaas.handler.ConnectionData +import com.github.creeper123123321.viaaas.mcLogger +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.ChannelPromise +import java.net.InetSocketAddress +import java.util.concurrent.TimeUnit +import io.netty.channel.ChannelDuplexHandler +import java.lang.Exception +import java.util.function.Consumer + +// https://github.com/ViaVersion/ViaFabric/blob/mc-1.16/src/main/java/com/github/creeper123123321/viafabric/handler/clientside/ProtocolDetectionHandler.java +class ProtocolDetectorHandler(val connectionData: ConnectionData) : ChannelDuplexHandler() { + private val queuedMessages = ArrayDeque>() + private var hold = true + private var pendentFlush = false + + @Throws(Exception::class) + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + if (ctx.channel().remoteAddress() is InetSocketAddress) { + val timeoutRun = ctx.executor().schedule({ + mcLogger.warn( + "Timeout for protocol auto-detection in " + + ctx.channel().remoteAddress() + " server" + ) + hold = false + drainQueue(ctx) + ctx.pipeline().remove(this) + }, 10, TimeUnit.SECONDS) + ProtocolDetector.detectVersion(ctx.channel().remoteAddress() as InetSocketAddress) + .whenComplete { protocol, _ -> + if (protocol != null && protocol.version != -1) { + connectionData.backVer = protocol.version + } else { + connectionData.backVer = 47 // fallback + } + + ctx.pipeline().remove(this) + timeoutRun.cancel(false) + } + // Let's cache it before we need it + } + } + + @Throws(Exception::class) + override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { + if (!hold) { + drainQueue(ctx) + super.write(ctx, msg, promise) + } else { + queuedMessages.add(Pair(msg, promise)) + } + } + + @Throws(Exception::class) + override fun flush(ctx: ChannelHandlerContext) { + if (!hold) { + drainQueue(ctx) + super.flush(ctx) + } else { + pendentFlush = true + } + } + + @Throws(Exception::class) + override fun channelInactive(ctx: ChannelHandlerContext) { + drainQueue(ctx) + super.channelInactive(ctx) + } + + private fun drainQueue(ctx: ChannelHandlerContext) { + queuedMessages.forEach(Consumer { + ctx.write( + it.first, + it.second + ) + }) + queuedMessages.clear() + if (pendentFlush) ctx.flush() + pendentFlush = false + } + + @Throws(Exception::class) + override fun handlerRemoved(ctx: ChannelHandlerContext) { + drainQueue(ctx) + super.handlerRemoved(ctx) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/HandshakeState.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/HandshakeState.kt index d0b210c..47a0339 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/HandshakeState.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/HandshakeState.kt @@ -45,7 +45,7 @@ class HandshakeState : MinecraftConnectionState { } val parsed = VIAaaSAddress().parse(packet.address.substringBefore(0.toChar()), VIAaaSConfig.hostName) - val backProto = parsed.protocol ?: 47 // todo autodetection + val backProto = parsed.protocol val hadHostname = parsed.viaSuffix != null packet.address = parsed.serverAddress!! @@ -61,7 +61,7 @@ class HandshakeState : MinecraftConnectionState { handler.data.backName = parsed.username val playerAddr = handler.data.frontHandler.remoteAddress - mcLogger.info("Connecting $playerAddr (${handler.data.frontVer}) -> ${packet.address}:${packet.port} ($backProto)") + mcLogger.info("Connecting ${handler.data.state.state} $playerAddr (${handler.data.frontVer}) -> ${packet.address}:${packet.port} ($backProto)") if (!hadHostname && VIAaaSConfig.requireHostName) { throw UnsupportedOperationException("This VIAaaS instance requires you to use the hostname") diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/PlayState.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/PlayState.kt index cf272ab..5ef1875 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/PlayState.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/PlayState.kt @@ -1,10 +1,18 @@ package com.github.creeper123123321.viaaas.handler.state -import com.github.creeper123123321.viaaas.packet.Packet -import com.github.creeper123123321.viaaas.packet.UnknownPacket +import com.github.creeper123123321.viaaas.config.VIAaaSConfig import com.github.creeper123123321.viaaas.handler.MinecraftHandler import com.github.creeper123123321.viaaas.handler.forward +import com.github.creeper123123321.viaaas.handler.is1_7 +import com.github.creeper123123321.viaaas.packet.Packet +import com.github.creeper123123321.viaaas.packet.UnknownPacket +import com.github.creeper123123321.viaaas.packet.play.PluginMessage +import com.github.creeper123123321.viaaas.readableToByteArray +import io.netty.buffer.ByteBufAllocator +import io.netty.buffer.Unpooled import io.netty.channel.ChannelHandlerContext +import us.myles.ViaVersion.api.protocol.ProtocolVersion +import us.myles.ViaVersion.api.type.Type import us.myles.ViaVersion.packets.State object PlayState : MinecraftConnectionState { @@ -12,10 +20,42 @@ object PlayState : MinecraftConnectionState { get() = State.PLAY override fun handlePacket(handler: MinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) { - if ((packet as UnknownPacket).id !in 0..127) throw IllegalArgumentException("Invalid packet id!") + when { + packet is UnknownPacket && (packet.id !in 0..127) -> throw IllegalArgumentException("Invalid packet id!") + packet is PluginMessage -> modifyPluginMessage(handler, packet) + } forward(handler, packet) } + private fun modifyPluginMessage(handler: MinecraftHandler, pluginMessage: PluginMessage) { + when (pluginMessage.channel) { + "MC|Brand", "brand", "minecraft:brand" -> { + if (!VIAaaSConfig.showBrandInfo) return + val brand = if (is1_7(handler)) { + String(pluginMessage.data, Charsets.UTF_8) + } else { + Type.STRING.read(Unpooled.wrappedBuffer(pluginMessage.data)) + } + " (VIAaaS C: ${ProtocolVersion.getProtocol(handler.data.frontVer!!)} S: ${ + ProtocolVersion.getProtocol( + handler.data.backVer!! + ) + })" + + if (is1_7(handler)) { + pluginMessage.data = brand.toByteArray(Charsets.UTF_8) + } else { + val buf = ByteBufAllocator.DEFAULT.buffer() + try { + Type.STRING.write(buf, brand) + pluginMessage.data = readableToByteArray(buf) + } finally { + buf.release() + } + } + } + } + } + override fun disconnect(handler: MinecraftHandler, msg: String) { super.disconnect(handler, msg) handler.data.frontChannel.close() diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/StatusState.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/StatusState.kt index 6ba4b95..22b99a4 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/StatusState.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/handler/state/StatusState.kt @@ -1,14 +1,20 @@ package com.github.creeper123123321.viaaas.handler.state -import com.github.creeper123123321.viaaas.packet.Packet -import com.github.creeper123123321.viaaas.packet.status.StatusResponse -import com.github.creeper123123321.viaaas.packet.UnknownPacket +import com.github.creeper123123321.viaaas.config.VIAaaSConfig import com.github.creeper123123321.viaaas.handler.MinecraftHandler import com.github.creeper123123321.viaaas.handler.forward +import com.github.creeper123123321.viaaas.packet.Packet +import com.github.creeper123123321.viaaas.packet.UnknownPacket +import com.github.creeper123123321.viaaas.packet.status.StatusResponse import com.github.creeper123123321.viaaas.writeFlushClose import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser import io.netty.channel.ChannelHandlerContext +import us.myles.ViaVersion.api.protocol.ProtocolVersion import us.myles.ViaVersion.packets.State +import java.util.* object StatusState : MinecraftConnectionState { override val state: State @@ -16,9 +22,31 @@ object StatusState : MinecraftConnectionState { override fun handlePacket(handler: MinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) { if (packet is UnknownPacket) throw IllegalArgumentException("Invalid packet") + when (packet) { + is StatusResponse -> modifyResponse(handler, packet) + } forward(handler, packet) } + private fun modifyResponse(handler: MinecraftHandler, packet: StatusResponse) { + if (VIAaaSConfig.showVersionPing) { + val parsed = JsonParser.parseString(packet.json).asJsonObject + val players = parsed.getAsJsonObject("players") ?: JsonObject().also { parsed.add("players", it) } + val sample = players.getAsJsonArray("sample") ?: JsonArray().also { players.add("sample", it) } + sample.add(JsonObject().also { + it.addProperty("id", UUID.nameUUIDFromBytes("VIAaaS".toByteArray(Charsets.UTF_8)).toString()) + it.addProperty( + "name", + "§9VIAaaS§r (C: §7${ProtocolVersion.getProtocol(handler.data.frontVer!!)}§r S: §7${ + ProtocolVersion.getProtocol(handler.data.backVer!!) + }§r)" + ) + }) + + packet.json = parsed.toString() + } + } + override fun disconnect(handler: MinecraftHandler, msg: String) { super.disconnect(handler, msg) diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/PacketRegistry.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/PacketRegistry.kt index 319e5c0..fa9585d 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/PacketRegistry.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/PacketRegistry.kt @@ -2,6 +2,7 @@ package com.github.creeper123123321.viaaas.packet import com.github.creeper123123321.viaaas.packet.handshake.Handshake import com.github.creeper123123321.viaaas.packet.login.* +import com.github.creeper123123321.viaaas.packet.play.PluginMessage import com.github.creeper123123321.viaaas.packet.status.StatusPing import com.github.creeper123123321.viaaas.packet.status.StatusPong import com.github.creeper123123321.viaaas.packet.status.StatusRequest @@ -17,6 +18,7 @@ object PacketRegistry { val entries = mutableListOf() init { + // Obviosly stolen from https://github.com/VelocityPowered/Velocity/blob/dev/1.1.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java register(Range.all(), State.HANDSHAKE, 0, true, ::Handshake) register(Range.all(), State.LOGIN, 0, true, ::LoginStart) register(Range.all(), State.LOGIN, 1, true, ::CryptoResponse) @@ -25,11 +27,33 @@ object PacketRegistry { register(Range.all(), State.LOGIN, 1, false, ::CryptoRequest) register(Range.all(), State.LOGIN, 2, false, ::LoginSuccess) register(Range.all(), State.LOGIN, 3, false, ::SetCompression) - register(Range.all(), State.LOGIN, 4, false, ::PluginRequest) + register(Range.atLeast(ProtocolVersion.v1_13.version), State.LOGIN, 4, false, ::PluginRequest) register(Range.all(), State.STATUS, 0, true, ::StatusRequest) register(Range.all(), State.STATUS, 1, true, ::StatusPing) register(Range.all(), State.STATUS, 0, false, ::StatusResponse) register(Range.all(), State.STATUS, 1, false, ::StatusPong) + register( + ::PluginMessage, State.PLAY, true, mapOf( + Range.closed(ProtocolVersion.v1_7_1.version, ProtocolVersion.v1_8.version) to 0x17, + Range.closed(ProtocolVersion.v1_9.version, ProtocolVersion.v1_11_1.version) to 0x09, + Range.singleton(ProtocolVersion.v1_12.version) to 0x0A, + Range.closed(ProtocolVersion.v1_12_1.version, ProtocolVersion.v1_12_2.version) to 0x09, + Range.closed(ProtocolVersion.v1_13.version, ProtocolVersion.v1_13_2.version) to 0x0A, + Range.closed(ProtocolVersion.v1_14.version, ProtocolVersion.v1_16_4.version) to 0x0B + ) + ) + register( + ::PluginMessage, State.PLAY, false, + mapOf( + Range.closed(ProtocolVersion.v1_7_1.version, ProtocolVersion.v1_8.version) to 0x3F, + Range.closed(ProtocolVersion.v1_9.version, ProtocolVersion.v1_12_2.version) to 0x18, + Range.closed(ProtocolVersion.v1_13.version, ProtocolVersion.v1_13_2.version) to 0x19, + Range.closed(ProtocolVersion.v1_14.version, ProtocolVersion.v1_14_4.version) to 0x18, + Range.closed(ProtocolVersion.v1_15.version, ProtocolVersion.v1_15_2.version) to 0x19, + Range.closed(ProtocolVersion.v1_16.version, ProtocolVersion.v1_16_1.version) to 0x18, + Range.closed(ProtocolVersion.v1_16_2.version, ProtocolVersion.v1_16_4.version) to 0x17 + ) + ) } inline fun register( @@ -42,6 +66,15 @@ object PacketRegistry { entries.add(RegistryEntry(protocol, state, id, serverBound, constructor, P::class.java)) } + inline fun register( + constructor: Supplier

, + state: State, + serverBound: Boolean, + idByProtocol: Map, Int> + ) { + idByProtocol.forEach { (protocol, id) -> register(protocol, state, id, serverBound, constructor) } + } + data class RegistryEntry( val versionRange: Range, val state: State, @@ -63,9 +96,9 @@ object PacketRegistry { }?.constructor } - fun getPacketId(packetClass: Class, protocolVersion: Int): Int? { + fun getPacketId(packetClass: Class, protocolVersion: Int, serverBound: Boolean): Int? { return entries.firstOrNull { - it.versionRange.contains(protocolVersion) && it.packetClass == packetClass + it.versionRange.contains(protocolVersion) && it.packetClass == packetClass && it.serverBound == serverBound }?.id } @@ -78,11 +111,11 @@ object PacketRegistry { return packet } - fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: Int) { + fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: Int, serverBound: Boolean) { val id = if (packet is UnknownPacket) { packet.id } else { - getPacketId(packet.javaClass, protocolVersion)!! + getPacketId(packet.javaClass, protocolVersion, serverBound)!! } Type.VAR_INT.writePrimitive(byteBuf, id) packet.encode(byteBuf, protocolVersion) diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/UnknownPacket.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/UnknownPacket.kt index 30ed108..1a3347b 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/UnknownPacket.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/UnknownPacket.kt @@ -1,12 +1,13 @@ package com.github.creeper123123321.viaaas.packet +import com.github.creeper123123321.viaaas.readableToByteArray import io.netty.buffer.ByteBuf class UnknownPacket(val id: Int) : Packet { lateinit var content: ByteArray override fun decode(byteBuf: ByteBuf, protocolVersion: Int) { - content = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) } + content = readableToByteArray(byteBuf) } override fun encode(byteBuf: ByteBuf, protocolVersion: Int) { diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginRequest.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginRequest.kt index 422bd19..ac197f4 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginRequest.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginRequest.kt @@ -1,6 +1,7 @@ package com.github.creeper123123321.viaaas.packet.login import com.github.creeper123123321.viaaas.packet.Packet +import com.github.creeper123123321.viaaas.readableToByteArray import io.netty.buffer.ByteBuf import us.myles.ViaVersion.api.type.Type import kotlin.properties.Delegates @@ -12,7 +13,7 @@ class PluginRequest : Packet { override fun decode(byteBuf: ByteBuf, protocolVersion: Int) { id = Type.VAR_INT.readPrimitive(byteBuf) channel = Type.STRING.read(byteBuf) - data = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) } + data = readableToByteArray(byteBuf) } override fun encode(byteBuf: ByteBuf, protocolVersion: Int) { diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginResponse.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginResponse.kt index 5661cd1..2241f39 100644 --- a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginResponse.kt +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/login/PluginResponse.kt @@ -1,6 +1,7 @@ package com.github.creeper123123321.viaaas.packet.login import com.github.creeper123123321.viaaas.packet.Packet +import com.github.creeper123123321.viaaas.readableToByteArray import io.netty.buffer.ByteBuf import us.myles.ViaVersion.api.type.Type import kotlin.properties.Delegates @@ -13,7 +14,7 @@ class PluginResponse : Packet { id = Type.VAR_INT.readPrimitive(byteBuf) success = byteBuf.readBoolean() if (success) { - data = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) } + data = readableToByteArray(byteBuf) } } diff --git a/src/main/kotlin/com/github/creeper123123321/viaaas/packet/play/PluginMessage.kt b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/play/PluginMessage.kt new file mode 100644 index 0000000..0cba63a --- /dev/null +++ b/src/main/kotlin/com/github/creeper123123321/viaaas/packet/play/PluginMessage.kt @@ -0,0 +1,52 @@ +package com.github.creeper123123321.viaaas.packet.play + +import com.github.creeper123123321.viaaas.packet.Packet +import com.github.creeper123123321.viaaas.readableToByteArray +import io.netty.buffer.ByteBuf +import us.myles.ViaVersion.api.protocol.ProtocolVersion +import us.myles.ViaVersion.api.type.Type + +class PluginMessage : Packet { + lateinit var channel: String + lateinit var data: ByteArray + + override fun decode(byteBuf: ByteBuf, protocolVersion: Int) { + channel = Type.STRING.read(byteBuf) + data = if (protocolVersion <= ProtocolVersion.v1_7_6.version) { + ByteArray(readExtendedForgeShort(byteBuf)).also { byteBuf.readBytes(it) } + } else { + readableToByteArray(byteBuf) + } + } + + override fun encode(byteBuf: ByteBuf, protocolVersion: Int) { + Type.STRING.write(byteBuf, channel) + if (protocolVersion <= ProtocolVersion.v1_7_6.version) { + writeExtendedForgeShort(byteBuf, data.size) + } + byteBuf.writeBytes(data) + } + + // stolen from https://github.com/VelocityPowered/Velocity/blob/27ccb9d387fc9a0aecd5c4b570d7d957558efddc/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java#L418 + fun readExtendedForgeShort(buf: ByteBuf): Int { + var low = buf.readUnsignedShort() + var high = 0 + if (low and 0x8000 != 0) { + low = low and 0x7FFF + high = buf.readUnsignedByte().toInt() + } + return high and 0xFF shl 15 or low + } + + fun writeExtendedForgeShort(buf: ByteBuf, toWrite: Int) { + var low = toWrite and 0x7FFF + val high = toWrite and 0x7F8000 shr 15 + if (high != 0) { + low = low or 0x8000 + } + buf.writeShort(low) + if (high != 0) { + buf.writeByte(high) + } + } +} \ No newline at end of file diff --git a/src/main/resources/viaaas.yml b/src/main/resources/viaaas.yml index c75fa68..3eaca87 100644 --- a/src/main/resources/viaaas.yml +++ b/src/main/resources/viaaas.yml @@ -27,4 +27,8 @@ blocked-back-addresses: ["*.hypixel.net", "hypixel.net"] # VIAaaS will only allow if it matches an address in this list allowed-back-addresses: ["*"] # Requires online mode for front-end connections. May be useful for stopping bots. -force-online-mode: false \ No newline at end of file +force-online-mode: false +# Shows player and server version in player list +show-version-ping: true +# Shows info in server brand (F3) +show-brand-info: true \ No newline at end of file