mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-22 12:05:45 +01:00
Implement force online/offline mode and offline name listening, create separated packet codec handler
This commit is contained in:
parent
c7a126cf61
commit
b41e29e5b7
@ -1,6 +1,7 @@
|
|||||||
package com.github.creeper123123321.viaaas
|
package com.github.creeper123123321.viaaas
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
|
import io.netty.buffer.ByteBufAllocator
|
||||||
import io.netty.channel.Channel
|
import io.netty.channel.Channel
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelInitializer
|
import io.netty.channel.ChannelInitializer
|
||||||
@ -27,6 +28,7 @@ object FrontChannelInit : ChannelInitializer<Channel>() {
|
|||||||
.addLast("frame", FrameCodec())
|
.addLast("frame", FrameCodec())
|
||||||
// "compress"
|
// "compress"
|
||||||
.addLast("flow-handler", FlowControlHandler())
|
.addLast("flow-handler", FlowControlHandler())
|
||||||
|
.addLast("mc", CloudMinecraftCodec())
|
||||||
.addLast(
|
.addLast(
|
||||||
"handler", CloudMinecraftHandler(
|
"handler", CloudMinecraftHandler(
|
||||||
ConnectionData(frontChannel = ch), other = null, frontEnd = true
|
ConnectionData(frontChannel = ch), other = null, frontEnd = true
|
||||||
@ -44,10 +46,34 @@ class BackendInit(val connectionData: ConnectionData) : ChannelInitializer<Chann
|
|||||||
.addLast("frame", FrameCodec())
|
.addLast("frame", FrameCodec())
|
||||||
// compress
|
// compress
|
||||||
.addLast("via-codec", CloudViaCodec(user))
|
.addLast("via-codec", CloudViaCodec(user))
|
||||||
|
.addLast("mc", CloudMinecraftCodec())
|
||||||
.addLast("handler", CloudMinecraftHandler(connectionData, connectionData.frontChannel, frontEnd = false))
|
.addLast("handler", CloudMinecraftHandler(connectionData, connectionData.frontChannel, frontEnd = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CloudMinecraftCodec: MessageToMessageCodec<ByteBuf, Packet>() {
|
||||||
|
override fun encode(ctx: ChannelHandlerContext, msg: Packet, out: MutableList<Any>) {
|
||||||
|
if (!ctx.channel().isActive) return
|
||||||
|
val buf = ByteBufAllocator.DEFAULT.buffer()
|
||||||
|
try {
|
||||||
|
val handler = ctx.pipeline().get(CloudMinecraftHandler::class.java)
|
||||||
|
PacketRegistry.encode(msg, buf, handler.data.frontVer!!)
|
||||||
|
out.add(buf.retain())
|
||||||
|
} finally {
|
||||||
|
buf.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||||
|
if (!ctx.channel().isActive || !msg.isReadable) return
|
||||||
|
val handler = ctx.pipeline().get(CloudMinecraftHandler::class.java)
|
||||||
|
out.add(PacketRegistry.decode(msg,
|
||||||
|
handler.data.frontVer ?: 0,
|
||||||
|
handler.data.state.state, handler.frontEnd))
|
||||||
|
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CloudCrypto(val cipherDecode: Cipher, var cipherEncode: Cipher) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
|
class CloudCrypto(val cipherDecode: Cipher, var cipherEncode: Cipher) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
|
||||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||||
val i = msg.readerIndex()
|
val i = msg.readerIndex()
|
||||||
@ -130,9 +156,9 @@ class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FrameCodec : ByteToMessageCodec<ByteBuf>() {
|
val badLength = DecoderException("Invalid length!")
|
||||||
val badLength = DecoderException("Invalid length!")
|
|
||||||
|
|
||||||
|
class FrameCodec : ByteToMessageCodec<ByteBuf>() {
|
||||||
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
|
override fun decode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
|
||||||
if (!ctx.channel().isActive) {
|
if (!ctx.channel().isActive) {
|
||||||
input.clear() // Ignore, should prevent DoS https://github.com/SpigotMC/BungeeCord/pull/2908
|
input.clear() // Ignore, should prevent DoS https://github.com/SpigotMC/BungeeCord/pull/2908
|
||||||
|
@ -6,7 +6,6 @@ import com.google.gson.Gson
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.netty.bootstrap.Bootstrap
|
import io.netty.bootstrap.Bootstrap
|
||||||
import io.netty.buffer.ByteBuf
|
|
||||||
import io.netty.buffer.ByteBufAllocator
|
import io.netty.buffer.ByteBufAllocator
|
||||||
import io.netty.channel.*
|
import io.netty.channel.*
|
||||||
import io.netty.channel.socket.SocketChannel
|
import io.netty.channel.socket.SocketChannel
|
||||||
@ -14,7 +13,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import us.myles.ViaVersion.api.protocol.ProtocolVersion
|
|
||||||
import us.myles.ViaVersion.api.type.Type
|
import us.myles.ViaVersion.api.type.Type
|
||||||
import us.myles.ViaVersion.exception.CancelCodecException
|
import us.myles.ViaVersion.exception.CancelCodecException
|
||||||
import us.myles.ViaVersion.packets.State
|
import us.myles.ViaVersion.packets.State
|
||||||
@ -26,6 +24,8 @@ import java.security.MessageDigest
|
|||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
@ -42,11 +42,6 @@ class ConnectionData(
|
|||||||
var frontOnline: Boolean? = null, // todo
|
var frontOnline: Boolean? = null, // todo
|
||||||
var frontName: String? = null,
|
var frontName: String? = null,
|
||||||
var backName: String? = null,
|
var backName: String? = null,
|
||||||
var backPublicKey: PublicKey? = null,
|
|
||||||
var backToken: ByteArray? = null,
|
|
||||||
var frontToken: ByteArray? = null,
|
|
||||||
var frontServerId: String? = null,
|
|
||||||
var backServerId: String? = null,
|
|
||||||
var frontVer: Int? = null,
|
var frontVer: Int? = null,
|
||||||
var backVer: Int? = null,
|
var backVer: Int? = null,
|
||||||
) {
|
) {
|
||||||
@ -58,13 +53,12 @@ class CloudMinecraftHandler(
|
|||||||
val data: ConnectionData,
|
val data: ConnectionData,
|
||||||
var other: Channel?,
|
var other: Channel?,
|
||||||
val frontEnd: Boolean
|
val frontEnd: Boolean
|
||||||
) : SimpleChannelInboundHandler<ByteBuf>() {
|
) : SimpleChannelInboundHandler<Packet>() {
|
||||||
var remoteAddress: SocketAddress? = null
|
var remoteAddress: SocketAddress? = null
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
override fun channelRead0(ctx: ChannelHandlerContext, packet: Packet) {
|
||||||
if (ctx.channel().isActive && msg.isReadable) {
|
if (ctx.channel().isActive) {
|
||||||
data.state.handleMessage(this, ctx, msg)
|
data.state.handlePacket(this, ctx, packet)
|
||||||
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,9 +91,10 @@ class CloudMinecraftHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MinecraftConnectionState {
|
interface MinecraftConnectionState {
|
||||||
fun handleMessage(
|
val state: State
|
||||||
|
fun handlePacket(
|
||||||
handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
||||||
msg: ByteBuf
|
packet: Packet
|
||||||
)
|
)
|
||||||
|
|
||||||
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||||
@ -112,19 +107,27 @@ interface MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HandshakeState : MinecraftConnectionState {
|
class HandshakeState : MinecraftConnectionState {
|
||||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
fun connectBack(handler: CloudMinecraftHandler, socketAddr: InetSocketAddress): ChannelFuture {
|
||||||
val packet = PacketRegistry.decode(
|
return Bootstrap()
|
||||||
msg,
|
.handler(BackendInit(handler.data))
|
||||||
ProtocolVersion.v1_8, // we still dont know what protocol it is
|
.channelFactory(channelSocketFactory())
|
||||||
State.HANDSHAKE,
|
.group(handler.data.frontChannel.eventLoop())
|
||||||
handler.frontEnd
|
.option(ChannelOption.IP_TOS, 0x18)
|
||||||
)
|
.option(ChannelOption.TCP_NODELAY, true)
|
||||||
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
||||||
|
.connect(socketAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val state: State
|
||||||
|
get() = State.HANDSHAKE
|
||||||
|
|
||||||
|
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||||
if (packet !is HandshakePacket) throw IllegalArgumentException("Invalid packet!")
|
if (packet !is HandshakePacket) throw IllegalArgumentException("Invalid packet!")
|
||||||
|
|
||||||
handler.data.frontVer = packet.protocolId
|
handler.data.frontVer = packet.protocolId
|
||||||
when (packet.nextState.ordinal) {
|
when (packet.nextState.ordinal) {
|
||||||
1 -> handler.data.state = StatusState
|
1 -> handler.data.state = StatusState
|
||||||
2 -> handler.data.state = LoginState
|
2 -> handler.data.state = LoginState()
|
||||||
else -> throw IllegalStateException("Invalid next state")
|
else -> throw IllegalStateException("Invalid next state")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,22 +164,18 @@ class HandshakeState : MinecraftConnectionState {
|
|||||||
|
|
||||||
if (checkLocalAddress(socketAddr.address)
|
if (checkLocalAddress(socketAddr.address)
|
||||||
|| matchesAddress(socketAddr, VIAaaSConfig.blockedBackAddresses)
|
|| matchesAddress(socketAddr, VIAaaSConfig.blockedBackAddresses)
|
||||||
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)) {
|
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)
|
||||||
|
) {
|
||||||
throw SecurityException("Not allowed")
|
throw SecurityException("Not allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
val bootstrap = Bootstrap()
|
val future = connectBack(handler, socketAddr)
|
||||||
.handler(BackendInit(handler.data))
|
|
||||||
.channelFactory(channelSocketFactory())
|
|
||||||
.group(handler.data.frontChannel.eventLoop())
|
|
||||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
|
||||||
.connect(socketAddr)
|
|
||||||
|
|
||||||
bootstrap.addListener {
|
future.addListener {
|
||||||
if (it.isSuccess) {
|
if (it.isSuccess) {
|
||||||
mcLogger.info("Connected ${handler.remoteAddress} -> $socketAddr")
|
mcLogger.info("Connected ${handler.remoteAddress} -> $socketAddr")
|
||||||
|
|
||||||
val backChan = bootstrap.channel() as SocketChannel
|
val backChan = future.channel() as SocketChannel
|
||||||
handler.data.backChannel = backChan
|
handler.data.backChannel = backChan
|
||||||
handler.other = backChan
|
handler.other = backChan
|
||||||
|
|
||||||
@ -205,15 +204,14 @@ class HandshakeState : MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object LoginState : MinecraftConnectionState {
|
class LoginState : MinecraftConnectionState {
|
||||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
val callbackPlayerId = CompletableFuture<String>()
|
||||||
val packet = PacketRegistry.decode(
|
lateinit var frontToken: ByteArray
|
||||||
msg,
|
lateinit var frontServerId: String
|
||||||
ProtocolVersion.getProtocol(handler.data.frontVer!!),
|
override val state: State
|
||||||
State.LOGIN,
|
get() = State.LOGIN
|
||||||
handler.frontEnd
|
|
||||||
)
|
|
||||||
|
|
||||||
|
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||||
when (packet) {
|
when (packet) {
|
||||||
is LoginStart -> handleLoginStart(handler, packet)
|
is LoginStart -> handleLoginStart(handler, packet)
|
||||||
is CryptoResponse -> handleCryptoResponse(handler, packet)
|
is CryptoResponse -> handleCryptoResponse(handler, packet)
|
||||||
@ -253,12 +251,7 @@ object LoginState : MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
|
fun authenticateOnlineFront(frontHandler: CloudMinecraftHandler) {
|
||||||
val data = handler.data
|
|
||||||
data.backServerId = cryptoRequest.serverId
|
|
||||||
data.backPublicKey = cryptoRequest.publicKey
|
|
||||||
data.backToken = cryptoRequest.token
|
|
||||||
|
|
||||||
val id = "VIAaaS" + ByteArray(10).let {
|
val id = "VIAaaS" + ByteArray(10).let {
|
||||||
secureRandom.nextBytes(it)
|
secureRandom.nextBytes(it)
|
||||||
Base64.getEncoder().withoutPadding().encodeToString(it)
|
Base64.getEncoder().withoutPadding().encodeToString(it)
|
||||||
@ -269,14 +262,66 @@ object LoginState : MinecraftConnectionState {
|
|||||||
secureRandom.nextBytes(it)
|
secureRandom.nextBytes(it)
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
data.frontToken = token
|
frontToken = token
|
||||||
data.frontServerId = id
|
frontServerId = id
|
||||||
|
|
||||||
|
val cryptoRequest = CryptoRequest()
|
||||||
cryptoRequest.serverId = id
|
cryptoRequest.serverId = id
|
||||||
cryptoRequest.publicKey = mcCryptoKey.public
|
cryptoRequest.publicKey = mcCryptoKey.public
|
||||||
cryptoRequest.token = token
|
cryptoRequest.token = token
|
||||||
|
|
||||||
forward(handler, cryptoRequest)
|
frontHandler.data.frontChannel.writeAndFlush(cryptoRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
|
||||||
|
val data = handler.data
|
||||||
|
val backServerId = cryptoRequest.serverId
|
||||||
|
val backPublicKey = cryptoRequest.publicKey
|
||||||
|
val backToken = cryptoRequest.token
|
||||||
|
|
||||||
|
if (data.frontOnline == null) {
|
||||||
|
authenticateOnlineFront(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
val backKey = ByteArray(16).let {
|
||||||
|
secureRandom.nextBytes(it)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
val backHash = generateServerHash(backServerId, backKey, backPublicKey)
|
||||||
|
|
||||||
|
callbackPlayerId.whenComplete { playerId, e ->
|
||||||
|
if (e != null) return@whenComplete
|
||||||
|
val frontHandler = handler.data.frontHandler
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val sessionJoin = viaWebServer.requestSessionJoin(
|
||||||
|
parseUndashedId(playerId),
|
||||||
|
handler.data.backName!!,
|
||||||
|
backHash,
|
||||||
|
frontHandler.remoteAddress!!, // Frontend handler
|
||||||
|
backPublicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
val backChan = handler.data.backChannel!!
|
||||||
|
sessionJoin.whenCompleteAsync({ _, throwable ->
|
||||||
|
if (throwable != null) {
|
||||||
|
frontHandler.data.backHandler!!.disconnect("Online mode error: $throwable")
|
||||||
|
} else {
|
||||||
|
val cryptoResponse = CryptoResponse()
|
||||||
|
cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey)
|
||||||
|
cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken)
|
||||||
|
forward(frontHandler, cryptoResponse, true)
|
||||||
|
|
||||||
|
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
||||||
|
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
||||||
|
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
|
||||||
|
}
|
||||||
|
}, backChan.eventLoop())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
frontHandler.disconnect("Online mode error: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleCryptoResponse(handler: CloudMinecraftHandler, cryptoResponse: CryptoResponse) {
|
fun handleCryptoResponse(handler: CloudMinecraftHandler, cryptoResponse: CryptoResponse) {
|
||||||
@ -285,23 +330,16 @@ object LoginState : MinecraftConnectionState {
|
|||||||
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
||||||
val decryptedToken = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedToken)
|
val decryptedToken = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedToken)
|
||||||
|
|
||||||
if (!decryptedToken.contentEquals(handler.data.frontToken!!)) throw IllegalStateException("invalid token!")
|
if (!decryptedToken.contentEquals(frontToken)) throw IllegalStateException("invalid token!")
|
||||||
|
|
||||||
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
|
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
|
||||||
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
||||||
|
|
||||||
handler.data.frontChannel.pipeline().addBefore("frame", "crypto", CloudCrypto(aesDe, aesEn))
|
handler.data.frontChannel.pipeline().addBefore("frame", "crypto", CloudCrypto(aesDe, aesEn))
|
||||||
|
|
||||||
generateServerHash(handler.data.frontServerId!!, frontKey, mcCryptoKey.public)
|
generateServerHash(frontServerId, frontKey, mcCryptoKey.public)
|
||||||
}
|
}
|
||||||
|
|
||||||
val backKey = ByteArray(16).let {
|
|
||||||
secureRandom.nextBytes(it)
|
|
||||||
it
|
|
||||||
}
|
|
||||||
|
|
||||||
val backHash = generateServerHash(handler.data.backServerId!!, backKey, handler.data.backPublicKey!!)
|
|
||||||
|
|
||||||
handler.data.frontChannel.setAutoRead(false)
|
handler.data.frontChannel.setAutoRead(false)
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@ -309,45 +347,40 @@ object LoginState : MinecraftConnectionState {
|
|||||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
|
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
|
||||||
UrlEscapers.urlFormParameterEscaper().escape(handler.data.frontName!!) +
|
UrlEscapers.urlFormParameterEscaper().escape(handler.data.frontName!!) +
|
||||||
"&serverId=$frontHash"
|
"&serverId=$frontHash"
|
||||||
)
|
) ?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
||||||
?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
|
||||||
|
|
||||||
val sessionJoin = viaWebServer.requestSessionJoin(
|
val id = profile.get("id")!!.asString
|
||||||
fromUndashed(profile.get("id")!!.asString),
|
mcLogger.info("Validated front-end session: ${handler.data.frontName} $id")
|
||||||
handler.data.backName!!,
|
callbackPlayerId.complete(id)
|
||||||
backHash,
|
|
||||||
handler.remoteAddress!!, // Frontend handler
|
|
||||||
handler.data.backPublicKey!!
|
|
||||||
)
|
|
||||||
|
|
||||||
val backChan = handler.other!!
|
|
||||||
sessionJoin.whenCompleteAsync({ _, throwable ->
|
|
||||||
if (throwable != null) {
|
|
||||||
handler.disconnect("Online mode error: $throwable")
|
|
||||||
} else {
|
|
||||||
cryptoResponse.encryptedKey = encryptRsa(handler.data.backPublicKey!!, backKey)
|
|
||||||
cryptoResponse.encryptedToken =
|
|
||||||
encryptRsa(handler.data.backPublicKey!!, handler.data.backToken!!)
|
|
||||||
forward(handler, cryptoResponse, true)
|
|
||||||
|
|
||||||
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
|
||||||
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
|
||||||
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
|
|
||||||
}
|
|
||||||
}, backChan.eventLoop())
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
handler.disconnect("Online mode error: $e")
|
callbackPlayerId.completeExceptionally(e)
|
||||||
}
|
}
|
||||||
handler.data.frontChannel.setAutoRead(true)
|
handler.data.frontChannel.setAutoRead(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleLoginStart(handler: CloudMinecraftHandler, loginStart: LoginStart) {
|
fun handleLoginStart(handler: CloudMinecraftHandler, loginStart: LoginStart) {
|
||||||
|
if (loginStart.username.length > 16) throw badLength
|
||||||
|
|
||||||
handler.data.frontName = loginStart.username
|
handler.data.frontName = loginStart.username
|
||||||
handler.data.backName = handler.data.backName ?: handler.data.frontName
|
handler.data.backName = handler.data.backName ?: handler.data.frontName
|
||||||
|
|
||||||
loginStart.username = handler.data.backName!!
|
loginStart.username = handler.data.backName!!
|
||||||
forward(handler, loginStart)
|
|
||||||
|
callbackPlayerId.whenComplete { _, e -> if (e != null) disconnect(handler, "Profile error: $e") }
|
||||||
|
|
||||||
|
if (handler.data.frontOnline == false) {
|
||||||
|
callbackPlayerId.complete(generateOfflinePlayerUuid(handler.data.frontName!!).toString().replace("-", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler.data.frontOnline == true) { // forced
|
||||||
|
authenticateOnlineFront(handler.data.backHandler!!)
|
||||||
|
callbackPlayerId.whenComplete { _, e ->
|
||||||
|
if (e == null) forward(handler, loginStart, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
forward(handler, loginStart)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||||
@ -367,12 +400,12 @@ object LoginState : MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object StatusState : MinecraftConnectionState {
|
object StatusState : MinecraftConnectionState {
|
||||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
override val state: State
|
||||||
val i = msg.readerIndex()
|
get() = State.STATUS
|
||||||
if (Type.VAR_INT.readPrimitive(msg) !in 0..1) throw IllegalArgumentException("Invalid packet id!")
|
|
||||||
msg.readerIndex(i)
|
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||||
forward(handler, msg.retainedSlice())
|
if ((packet as UnknownPacket).id !in 0..1) throw IllegalArgumentException("Invalid packet id!")
|
||||||
msg.clear()
|
forward(handler, packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||||
@ -394,12 +427,12 @@ object StatusState : MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object PlayState : MinecraftConnectionState {
|
object PlayState : MinecraftConnectionState {
|
||||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
override val state: State
|
||||||
val i = msg.readerIndex()
|
get() = State.PLAY
|
||||||
if (Type.VAR_INT.readPrimitive(msg) !in 0..127) throw IllegalArgumentException("Invalid packet id!")
|
|
||||||
msg.readerIndex(i)
|
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||||
forward(handler, msg.retainedSlice())
|
if ((packet as UnknownPacket).id !in 0..127) throw IllegalArgumentException("Invalid packet id!")
|
||||||
msg.clear()
|
forward(handler, packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||||
@ -449,22 +482,17 @@ fun generateServerHash(serverId: String, sharedSecret: ByteArray?, key: PublicKe
|
|||||||
private fun forward(handler: CloudMinecraftHandler, packet: Packet, flush: Boolean = false) {
|
private fun forward(handler: CloudMinecraftHandler, packet: Packet, flush: Boolean = false) {
|
||||||
val msg = ByteBufAllocator.DEFAULT.buffer()
|
val msg = ByteBufAllocator.DEFAULT.buffer()
|
||||||
try {
|
try {
|
||||||
PacketRegistry.encode(packet, msg, ProtocolVersion.getProtocol(handler.data.frontVer!!))
|
val ch = handler.other!!
|
||||||
forward(handler, msg.retain(), flush)
|
if (flush) {
|
||||||
|
ch.writeAndFlush(packet, ch.voidPromise())
|
||||||
|
} else {
|
||||||
|
ch.write(packet, ch.voidPromise())
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
msg.release()
|
msg.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun forward(handler: CloudMinecraftHandler, byteBuf: ByteBuf, flush: Boolean = false) {
|
|
||||||
val ch = handler.other!!
|
|
||||||
if (flush) {
|
|
||||||
ch.writeAndFlush(byteBuf, ch.voidPromise())
|
|
||||||
} else {
|
|
||||||
ch.write(byteBuf, ch.voidPromise())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resolveSrv(address: String, port: Int): Pair<String, Int> {
|
private fun resolveSrv(address: String, port: Int): Pair<String, Int> {
|
||||||
if (port == 25565) {
|
if (port == 25565) {
|
||||||
try {
|
try {
|
||||||
@ -509,3 +537,7 @@ private fun matchAddress(addr: String, list: List<String>): Boolean {
|
|||||||
list.contains(query)
|
list.contains(query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/VelocityPowered/Velocity/blob/e3f17eeb245b8d570f16c1f2aff5e7eafb698d5e/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java
|
||||||
|
fun generateOfflinePlayerUuid(username: String) =
|
||||||
|
UUID.nameUUIDFromBytes("OfflinePlayer:$username".toByteArray(Charsets.UTF_8))
|
@ -16,8 +16,8 @@ import kotlin.properties.Delegates
|
|||||||
* A mutable object which represents a Minecraft packet data
|
* A mutable object which represents a Minecraft packet data
|
||||||
*/
|
*/
|
||||||
interface Packet {
|
interface Packet {
|
||||||
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
|
fun decode(byteBuf: ByteBuf, protocolVersion: Int)
|
||||||
fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
|
fun encode(byteBuf: ByteBuf, protocolVersion: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
object PacketRegistry {
|
object PacketRegistry {
|
||||||
@ -70,24 +70,24 @@ object PacketRegistry {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun getPacketConstructor(
|
fun getPacketConstructor(
|
||||||
protocolVersion: ProtocolVersion,
|
protocolVersion: Int,
|
||||||
state: State,
|
state: State,
|
||||||
id: Int,
|
id: Int,
|
||||||
serverBound: Boolean
|
serverBound: Boolean
|
||||||
): Supplier<out Packet>? {
|
): Supplier<out Packet>? {
|
||||||
return entries.firstOrNull {
|
return entries.firstOrNull {
|
||||||
it.serverBound == serverBound && it.state == state
|
it.serverBound == serverBound && it.state == state
|
||||||
&& it.versionRange.contains(protocolVersion.version) && it.id == id
|
&& it.versionRange.contains(protocolVersion) && it.id == id
|
||||||
}?.constructor
|
}?.constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: ProtocolVersion): Int? {
|
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: Int): Int? {
|
||||||
return entries.firstOrNull {
|
return entries.firstOrNull {
|
||||||
it.versionRange.contains(protocolVersion.version) && it.packetClass == packetClass
|
it.versionRange.contains(protocolVersion) && it.packetClass == packetClass
|
||||||
}?.id
|
}?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion, state: State, serverBound: Boolean): Packet {
|
fun decode(byteBuf: ByteBuf, protocolVersion: Int, state: State, serverBound: Boolean): Packet {
|
||||||
val packetId = Type.VAR_INT.readPrimitive(byteBuf)
|
val packetId = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
val packet =
|
val packet =
|
||||||
getPacketConstructor(protocolVersion, state, packetId, serverBound)?.get() ?: UnknownPacket(packetId)
|
getPacketConstructor(protocolVersion, state, packetId, serverBound)?.get() ?: UnknownPacket(packetId)
|
||||||
@ -96,7 +96,7 @@ object PacketRegistry {
|
|||||||
return packet
|
return packet
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
val id = if (packet is UnknownPacket) {
|
val id = if (packet is UnknownPacket) {
|
||||||
packet.id
|
packet.id
|
||||||
} else {
|
} else {
|
||||||
@ -108,13 +108,13 @@ object PacketRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UnknownPacket(val id: Int) : Packet {
|
class UnknownPacket(val id: Int) : Packet {
|
||||||
lateinit var content: ByteBuf
|
lateinit var content: ByteArray
|
||||||
|
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
content = byteBuf.slice().clear()
|
content = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
byteBuf.writeBytes(content)
|
byteBuf.writeBytes(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,14 +127,14 @@ class HandshakePacket : Packet {
|
|||||||
var port by Delegates.notNull<Int>()
|
var port by Delegates.notNull<Int>()
|
||||||
lateinit var nextState: State
|
lateinit var nextState: State
|
||||||
|
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
protocolId = Type.VAR_INT.readPrimitive(byteBuf)
|
protocolId = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
address = Type.STRING.read(byteBuf)
|
address = Type.STRING.read(byteBuf)
|
||||||
port = byteBuf.readUnsignedShort()
|
port = byteBuf.readUnsignedShort()
|
||||||
nextState = State.values()[Type.VAR_INT.readPrimitive(byteBuf)]
|
nextState = State.values()[Type.VAR_INT.readPrimitive(byteBuf)]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.VAR_INT.writePrimitive(byteBuf, protocolId)
|
Type.VAR_INT.writePrimitive(byteBuf, protocolId)
|
||||||
Type.STRING.write(byteBuf, address)
|
Type.STRING.write(byteBuf, address)
|
||||||
byteBuf.writeShort(port)
|
byteBuf.writeShort(port)
|
||||||
@ -145,11 +145,11 @@ class HandshakePacket : Packet {
|
|||||||
class LoginStart : Packet {
|
class LoginStart : Packet {
|
||||||
lateinit var username: String
|
lateinit var username: String
|
||||||
|
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
username = Type.STRING.read(byteBuf)
|
username = Type.STRING.read(byteBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.STRING.write(byteBuf, username)
|
Type.STRING.write(byteBuf, username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,8 +158,8 @@ class CryptoResponse : Packet {
|
|||||||
lateinit var encryptedKey: ByteArray
|
lateinit var encryptedKey: ByteArray
|
||||||
lateinit var encryptedToken: ByteArray
|
lateinit var encryptedToken: ByteArray
|
||||||
|
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||||
encryptedKey = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
encryptedKey = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||||
encryptedToken = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
encryptedToken = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||||
} else {
|
} else {
|
||||||
@ -168,8 +168,8 @@ class CryptoResponse : Packet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedKey)
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedKey)
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedToken)
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedToken)
|
||||||
} else {
|
} else {
|
||||||
@ -184,16 +184,16 @@ class CryptoResponse : Packet {
|
|||||||
class PluginResponse : Packet {
|
class PluginResponse : Packet {
|
||||||
var id by Delegates.notNull<Int>()
|
var id by Delegates.notNull<Int>()
|
||||||
var success by Delegates.notNull<Boolean>()
|
var success by Delegates.notNull<Boolean>()
|
||||||
lateinit var data: ByteBuf
|
lateinit var data: ByteArray
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
id = Type.VAR_INT.readPrimitive(byteBuf)
|
id = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
success = byteBuf.readBoolean()
|
success = byteBuf.readBoolean()
|
||||||
if (success) {
|
if (success) {
|
||||||
data = byteBuf.slice().clear()
|
data = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.VAR_INT.writePrimitive(byteBuf, id)
|
Type.VAR_INT.writePrimitive(byteBuf, id)
|
||||||
byteBuf.writeBoolean(success)
|
byteBuf.writeBoolean(success)
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -204,11 +204,11 @@ class PluginResponse : Packet {
|
|||||||
|
|
||||||
class LoginDisconnect : Packet {
|
class LoginDisconnect : Packet {
|
||||||
lateinit var msg: String
|
lateinit var msg: String
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
msg = Type.STRING.read(byteBuf)
|
msg = Type.STRING.read(byteBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.STRING.write(byteBuf, msg)
|
Type.STRING.write(byteBuf, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,9 +218,9 @@ class CryptoRequest : Packet {
|
|||||||
lateinit var publicKey: PublicKey
|
lateinit var publicKey: PublicKey
|
||||||
lateinit var token: ByteArray
|
lateinit var token: ByteArray
|
||||||
|
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
serverId = Type.STRING.read(byteBuf)
|
serverId = Type.STRING.read(byteBuf)
|
||||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||||
publicKey = KeyFactory.getInstance("RSA")
|
publicKey = KeyFactory.getInstance("RSA")
|
||||||
.generatePublic(X509EncodedKeySpec(Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)))
|
.generatePublic(X509EncodedKeySpec(Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)))
|
||||||
token = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
token = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||||
@ -231,9 +231,9 @@ class CryptoRequest : Packet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.STRING.write(byteBuf, serverId)
|
Type.STRING.write(byteBuf, serverId)
|
||||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, publicKey.encoded)
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, publicKey.encoded)
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, token)
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, token)
|
||||||
} else {
|
} else {
|
||||||
@ -250,25 +250,25 @@ class LoginSuccess : Packet {
|
|||||||
lateinit var id: UUID
|
lateinit var id: UUID
|
||||||
lateinit var username: String
|
lateinit var username: String
|
||||||
|
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
id = when {
|
id = when {
|
||||||
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
|
protocolVersion >= ProtocolVersion.v1_16.version -> {
|
||||||
Type.UUID_INT_ARRAY.read(byteBuf)
|
Type.UUID_INT_ARRAY.read(byteBuf)
|
||||||
}
|
}
|
||||||
protocolVersion.version >= ProtocolVersion.v1_7_6.version -> {
|
protocolVersion >= ProtocolVersion.v1_7_6.version -> {
|
||||||
UUID.fromString(Type.STRING.read(byteBuf))
|
UUID.fromString(Type.STRING.read(byteBuf))
|
||||||
}
|
}
|
||||||
else -> fromUndashed(Type.STRING.read(byteBuf))
|
else -> parseUndashedId(Type.STRING.read(byteBuf))
|
||||||
}
|
}
|
||||||
username = Type.STRING.read(byteBuf)
|
username = Type.STRING.read(byteBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
when {
|
when {
|
||||||
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
|
protocolVersion >= ProtocolVersion.v1_16.version -> {
|
||||||
Type.UUID_INT_ARRAY.write(byteBuf, id)
|
Type.UUID_INT_ARRAY.write(byteBuf, id)
|
||||||
}
|
}
|
||||||
protocolVersion.version >= ProtocolVersion.v1_7_6.version -> {
|
protocolVersion >= ProtocolVersion.v1_7_6.version -> {
|
||||||
Type.STRING.write(byteBuf, id.toString())
|
Type.STRING.write(byteBuf, id.toString())
|
||||||
}
|
}
|
||||||
else -> Type.STRING.write(byteBuf, id.toString().replace("-", ""))
|
else -> Type.STRING.write(byteBuf, id.toString().replace("-", ""))
|
||||||
@ -279,11 +279,11 @@ class LoginSuccess : Packet {
|
|||||||
|
|
||||||
class SetCompression : Packet {
|
class SetCompression : Packet {
|
||||||
var threshold by Delegates.notNull<Int>()
|
var threshold by Delegates.notNull<Int>()
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
threshold = Type.VAR_INT.readPrimitive(byteBuf)
|
threshold = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.VAR_INT.writePrimitive(byteBuf, threshold)
|
Type.VAR_INT.writePrimitive(byteBuf, threshold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,14 +291,14 @@ class SetCompression : Packet {
|
|||||||
class PluginRequest : Packet {
|
class PluginRequest : Packet {
|
||||||
var id by Delegates.notNull<Int>()
|
var id by Delegates.notNull<Int>()
|
||||||
lateinit var channel: String
|
lateinit var channel: String
|
||||||
lateinit var data: ByteBuf
|
lateinit var data: ByteArray
|
||||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
id = Type.VAR_INT.readPrimitive(byteBuf)
|
id = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
channel = Type.STRING.read(byteBuf)
|
channel = Type.STRING.read(byteBuf)
|
||||||
data = byteBuf.slice().clear()
|
data = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||||
Type.VAR_INT.writePrimitive(byteBuf, id)
|
Type.VAR_INT.writePrimitive(byteBuf, id)
|
||||||
Type.STRING.write(byteBuf, channel)
|
Type.STRING.write(byteBuf, channel)
|
||||||
byteBuf.writeBytes(data)
|
byteBuf.writeBytes(data)
|
||||||
|
@ -4,20 +4,15 @@ import com.google.common.base.Preconditions
|
|||||||
import com.google.common.cache.CacheBuilder
|
import com.google.common.cache.CacheBuilder
|
||||||
import com.google.common.cache.CacheLoader
|
import com.google.common.cache.CacheLoader
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.client.features.json.*
|
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.forms.*
|
||||||
import io.ktor.features.*
|
import io.ktor.features.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.http.cio.websocket.*
|
import io.ktor.http.cio.websocket.*
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.request.*
|
|
||||||
import io.ktor.response.*
|
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
import io.ktor.util.*
|
|
||||||
import io.ktor.websocket.*
|
import io.ktor.websocket.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.consumeEach
|
import kotlinx.coroutines.channels.consumeEach
|
||||||
@ -61,7 +56,7 @@ class ViaWebApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
webLogger.info("${call.request.local.remoteHost} (O: ${call.request.origin.remoteHost}) exception: $e")
|
||||||
viaWebServer.onException(this, e)
|
viaWebServer.onException(this, e)
|
||||||
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "INTERNAL ERROR"))
|
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "INTERNAL ERROR"))
|
||||||
} finally {
|
} finally {
|
||||||
@ -78,7 +73,7 @@ class ViaWebApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/VelocityPowered/Velocity/blob/6467335f74a7d1617512a55cc9acef5e109b51ac/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java
|
// https://github.com/VelocityPowered/Velocity/blob/6467335f74a7d1617512a55cc9acef5e109b51ac/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java
|
||||||
fun fromUndashed(string: String): UUID {
|
fun parseUndashedId(string: String): UUID {
|
||||||
Preconditions.checkArgument(string.length == 32, "Length is incorrect")
|
Preconditions.checkArgument(string.length == 32, "Length is incorrect")
|
||||||
return UUID(
|
return UUID(
|
||||||
java.lang.Long.parseUnsignedLong(string.substring(0, 16), 16),
|
java.lang.Long.parseUnsignedLong(string.substring(0, 16), 16),
|
||||||
@ -100,7 +95,7 @@ class WebDashboardServer {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
httpClient.get<JsonObject?>("https://api.mojang.com/users/profiles/minecraft/$name")
|
httpClient.get<JsonObject?>("https://api.mojang.com/users/profiles/minecraft/$name")
|
||||||
?.get("id")?.asString?.let { fromUndashed(it) }
|
?.get("id")?.asString?.let { parseUndashedId(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -184,9 +179,23 @@ class WebLogin : WebState {
|
|||||||
val obj = Gson().fromJson(msg, JsonObject::class.java)
|
val obj = Gson().fromJson(msg, JsonObject::class.java)
|
||||||
|
|
||||||
when (obj.getAsJsonPrimitive("action").asString) {
|
when (obj.getAsJsonPrimitive("action").asString) {
|
||||||
|
"offline_login" -> {
|
||||||
|
// todo add some spam check
|
||||||
|
val username = obj.get("username").asString
|
||||||
|
val token = UUID.randomUUID()
|
||||||
|
val uuid = generateOfflinePlayerUuid(username)
|
||||||
|
|
||||||
|
webClient.server.loginTokens.put(token, uuid)
|
||||||
|
webClient.ws.send(
|
||||||
|
"""{"action": "login_result", "success": true,
|
||||||
|
| "username": "$username", "uuid": "$uuid", "token": "$token"}""".trimMargin()
|
||||||
|
)
|
||||||
|
|
||||||
|
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) generated a token for offline account $username")
|
||||||
|
}
|
||||||
"minecraft_id_login" -> {
|
"minecraft_id_login" -> {
|
||||||
val username = obj.getAsJsonPrimitive("username").asString
|
val username = obj.get("username").asString
|
||||||
val code = obj.getAsJsonPrimitive("code").asString
|
val code = obj.get("code").asString
|
||||||
|
|
||||||
val check = httpClient.submitForm<JsonObject>(
|
val check = httpClient.submitForm<JsonObject>(
|
||||||
"https://api.minecraft.id/gateway/verify/${URLEncoder.encode(username, Charsets.UTF_8)}",
|
"https://api.minecraft.id/gateway/verify/${URLEncoder.encode(username, Charsets.UTF_8)}",
|
||||||
@ -200,13 +209,13 @@ class WebLogin : WebState {
|
|||||||
|
|
||||||
webClient.server.loginTokens.put(token, uuid)
|
webClient.server.loginTokens.put(token, uuid)
|
||||||
webClient.ws.send(
|
webClient.ws.send(
|
||||||
"""{"action": "minecraft_id_result", "success": true,
|
"""{"action": "login_result", "success": true,
|
||||||
| "username": "$mcIdUser", "uuid": "$uuid", "token": "$token"}""".trimMargin()
|
| "username": "$mcIdUser", "uuid": "$uuid", "token": "$token"}""".trimMargin()
|
||||||
)
|
)
|
||||||
|
|
||||||
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) generated a token for account $mcIdUser $uuid")
|
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) generated a token for account $mcIdUser $uuid")
|
||||||
} else {
|
} else {
|
||||||
webClient.ws.send("""{"action": "minecraft_id_result", "success": false}""")
|
webClient.ws.send("""{"action": "login_result", "success": false}""")
|
||||||
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) failed to generated a token for account $username")
|
webLogger.info("${webClient.ws.call.request.local.remoteHost} (O: ${webClient.ws.call.request.origin.remoteHost}) failed to generated a token for account $username")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,44 +219,48 @@ function renderActions() {
|
|||||||
actions.innerHTML = "";
|
actions.innerHTML = "";
|
||||||
if (listenVisible) {
|
if (listenVisible) {
|
||||||
if (username != null && mcauth_code != null) {
|
if (username != null && mcauth_code != null) {
|
||||||
let p = document.createElement("p");
|
addAction("Listen to " + username, () => {
|
||||||
let add = document.createElement("a");
|
|
||||||
p.appendChild(add);
|
|
||||||
add.innerText = "Listen to " + username;
|
|
||||||
add.href = "#";
|
|
||||||
add.onclick = () => {
|
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
"action": "minecraft_id_login",
|
"action": "minecraft_id_login",
|
||||||
"username": username,
|
"username": username,
|
||||||
"code": mcauth_code}));
|
"code": mcauth_code}));
|
||||||
mcauth_code = null;
|
mcauth_code = null;
|
||||||
};
|
});
|
||||||
actions.appendChild(p);
|
|
||||||
}
|
}
|
||||||
let p = document.createElement("p");
|
addAction("Listen to premium login in VIAaaS instance", () => {
|
||||||
let link = document.createElement("a");
|
let user = prompt("Premium username (case-sensitive): ", "");
|
||||||
p.appendChild(link);
|
|
||||||
link.innerText = "Listen to username in VIAaaS instance";
|
|
||||||
link.href = "#";
|
|
||||||
link.onclick = () => {
|
|
||||||
let user = prompt("Username (Minecraft.ID is case-sensitive): ", "");
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
let callbackUrl = new URL(location.origin + location.pathname + "#username=" + encodeURIComponent(user));
|
let callbackUrl = new URL(location.origin + location.pathname + "#username=" + encodeURIComponent(user));
|
||||||
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + "?callback=" + encodeURIComponent(callbackUrl);
|
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||||
};
|
+ "?callback=" + encodeURIComponent(callbackUrl);
|
||||||
actions.appendChild(p);
|
});
|
||||||
|
addAction("Listen to offline login in VIAaaS instance", () => {
|
||||||
|
let user = prompt("Offline username (case-sensitive):", "");
|
||||||
|
if (!user) return;
|
||||||
|
socket.send(JSON.stringify({"action": "offline_login", "username": user}));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addAction(text, onClick) {
|
||||||
|
let p = document.createElement("p");
|
||||||
|
let link = document.createElement("a");
|
||||||
|
p.appendChild(link);
|
||||||
|
link.innerText = text;
|
||||||
|
link.href = "#";
|
||||||
|
link.onclick = onClick;
|
||||||
|
actions.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
function onSocketMsg(event) {
|
function onSocketMsg(event) {
|
||||||
console.log(event.data.toString());
|
console.log(event.data.toString());
|
||||||
let parsed = JSON.parse(event.data);
|
let parsed = JSON.parse(event.data);
|
||||||
if (parsed.action == "ad_minecraft_id_login") {
|
if (parsed.action == "ad_minecraft_id_login") {
|
||||||
listenVisible = true;
|
listenVisible = true;
|
||||||
renderActions();
|
renderActions();
|
||||||
} else if (parsed.action == "minecraft_id_result") {
|
} else if (parsed.action == "login_result") {
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
alert("VIAaaS instance couldn't verify account via Minecraft.ID");
|
alert("VIAaaS instance couldn't verify Minecraft account");
|
||||||
} else {
|
} else {
|
||||||
listen(parsed.token);
|
listen(parsed.token);
|
||||||
saveToken(parsed.token);
|
saveToken(parsed.token);
|
||||||
|
Loading…
Reference in New Issue
Block a user