mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-21 11:55:15 +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
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.ByteBufAllocator
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.channel.ChannelInitializer
|
||||
@ -27,6 +28,7 @@ object FrontChannelInit : ChannelInitializer<Channel>() {
|
||||
.addLast("frame", FrameCodec())
|
||||
// "compress"
|
||||
.addLast("flow-handler", FlowControlHandler())
|
||||
.addLast("mc", CloudMinecraftCodec())
|
||||
.addLast(
|
||||
"handler", CloudMinecraftHandler(
|
||||
ConnectionData(frontChannel = ch), other = null, frontEnd = true
|
||||
@ -44,10 +46,34 @@ class BackendInit(val connectionData: ConnectionData) : ChannelInitializer<Chann
|
||||
.addLast("frame", FrameCodec())
|
||||
// compress
|
||||
.addLast("via-codec", CloudViaCodec(user))
|
||||
.addLast("mc", CloudMinecraftCodec())
|
||||
.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>() {
|
||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||
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>) {
|
||||
if (!ctx.channel().isActive) {
|
||||
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 io.ktor.client.request.*
|
||||
import io.netty.bootstrap.Bootstrap
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.ByteBufAllocator
|
||||
import io.netty.channel.*
|
||||
import io.netty.channel.socket.SocketChannel
|
||||
@ -14,7 +13,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import us.myles.ViaVersion.api.protocol.ProtocolVersion
|
||||
import us.myles.ViaVersion.api.type.Type
|
||||
import us.myles.ViaVersion.exception.CancelCodecException
|
||||
import us.myles.ViaVersion.packets.State
|
||||
@ -26,6 +24,8 @@ import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
@ -42,11 +42,6 @@ class ConnectionData(
|
||||
var frontOnline: Boolean? = null, // todo
|
||||
var frontName: 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 backVer: Int? = null,
|
||||
) {
|
||||
@ -58,13 +53,12 @@ class CloudMinecraftHandler(
|
||||
val data: ConnectionData,
|
||||
var other: Channel?,
|
||||
val frontEnd: Boolean
|
||||
) : SimpleChannelInboundHandler<ByteBuf>() {
|
||||
) : SimpleChannelInboundHandler<Packet>() {
|
||||
var remoteAddress: SocketAddress? = null
|
||||
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
if (ctx.channel().isActive && msg.isReadable) {
|
||||
data.state.handleMessage(this, ctx, msg)
|
||||
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, packet: Packet) {
|
||||
if (ctx.channel().isActive) {
|
||||
data.state.handlePacket(this, ctx, packet)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,9 +91,10 @@ class CloudMinecraftHandler(
|
||||
}
|
||||
|
||||
interface MinecraftConnectionState {
|
||||
fun handleMessage(
|
||||
val state: State
|
||||
fun handlePacket(
|
||||
handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
||||
msg: ByteBuf
|
||||
packet: Packet
|
||||
)
|
||||
|
||||
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||
@ -112,19 +107,27 @@ interface MinecraftConnectionState {
|
||||
}
|
||||
|
||||
class HandshakeState : MinecraftConnectionState {
|
||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
val packet = PacketRegistry.decode(
|
||||
msg,
|
||||
ProtocolVersion.v1_8, // we still dont know what protocol it is
|
||||
State.HANDSHAKE,
|
||||
handler.frontEnd
|
||||
)
|
||||
fun connectBack(handler: CloudMinecraftHandler, socketAddr: InetSocketAddress): ChannelFuture {
|
||||
return Bootstrap()
|
||||
.handler(BackendInit(handler.data))
|
||||
.channelFactory(channelSocketFactory())
|
||||
.group(handler.data.frontChannel.eventLoop())
|
||||
.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!")
|
||||
|
||||
handler.data.frontVer = packet.protocolId
|
||||
when (packet.nextState.ordinal) {
|
||||
1 -> handler.data.state = StatusState
|
||||
2 -> handler.data.state = LoginState
|
||||
2 -> handler.data.state = LoginState()
|
||||
else -> throw IllegalStateException("Invalid next state")
|
||||
}
|
||||
|
||||
@ -161,22 +164,18 @@ class HandshakeState : MinecraftConnectionState {
|
||||
|
||||
if (checkLocalAddress(socketAddr.address)
|
||||
|| matchesAddress(socketAddr, VIAaaSConfig.blockedBackAddresses)
|
||||
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)) {
|
||||
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)
|
||||
) {
|
||||
throw SecurityException("Not allowed")
|
||||
}
|
||||
|
||||
val bootstrap = Bootstrap()
|
||||
.handler(BackendInit(handler.data))
|
||||
.channelFactory(channelSocketFactory())
|
||||
.group(handler.data.frontChannel.eventLoop())
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
||||
.connect(socketAddr)
|
||||
val future = connectBack(handler, socketAddr)
|
||||
|
||||
bootstrap.addListener {
|
||||
future.addListener {
|
||||
if (it.isSuccess) {
|
||||
mcLogger.info("Connected ${handler.remoteAddress} -> $socketAddr")
|
||||
|
||||
val backChan = bootstrap.channel() as SocketChannel
|
||||
val backChan = future.channel() as SocketChannel
|
||||
handler.data.backChannel = backChan
|
||||
handler.other = backChan
|
||||
|
||||
@ -205,15 +204,14 @@ class HandshakeState : MinecraftConnectionState {
|
||||
}
|
||||
}
|
||||
|
||||
object LoginState : MinecraftConnectionState {
|
||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
val packet = PacketRegistry.decode(
|
||||
msg,
|
||||
ProtocolVersion.getProtocol(handler.data.frontVer!!),
|
||||
State.LOGIN,
|
||||
handler.frontEnd
|
||||
)
|
||||
class LoginState : MinecraftConnectionState {
|
||||
val callbackPlayerId = CompletableFuture<String>()
|
||||
lateinit var frontToken: ByteArray
|
||||
lateinit var frontServerId: String
|
||||
override val state: State
|
||||
get() = State.LOGIN
|
||||
|
||||
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||
when (packet) {
|
||||
is LoginStart -> handleLoginStart(handler, packet)
|
||||
is CryptoResponse -> handleCryptoResponse(handler, packet)
|
||||
@ -253,12 +251,7 @@ object LoginState : MinecraftConnectionState {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
|
||||
val data = handler.data
|
||||
data.backServerId = cryptoRequest.serverId
|
||||
data.backPublicKey = cryptoRequest.publicKey
|
||||
data.backToken = cryptoRequest.token
|
||||
|
||||
fun authenticateOnlineFront(frontHandler: CloudMinecraftHandler) {
|
||||
val id = "VIAaaS" + ByteArray(10).let {
|
||||
secureRandom.nextBytes(it)
|
||||
Base64.getEncoder().withoutPadding().encodeToString(it)
|
||||
@ -269,14 +262,66 @@ object LoginState : MinecraftConnectionState {
|
||||
secureRandom.nextBytes(it)
|
||||
it
|
||||
}
|
||||
data.frontToken = token
|
||||
data.frontServerId = id
|
||||
frontToken = token
|
||||
frontServerId = id
|
||||
|
||||
val cryptoRequest = CryptoRequest()
|
||||
cryptoRequest.serverId = id
|
||||
cryptoRequest.publicKey = mcCryptoKey.public
|
||||
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) {
|
||||
@ -285,23 +330,16 @@ object LoginState : MinecraftConnectionState {
|
||||
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
||||
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 aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
||||
|
||||
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)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@ -309,45 +347,40 @@ object LoginState : MinecraftConnectionState {
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
|
||||
UrlEscapers.urlFormParameterEscaper().escape(handler.data.frontName!!) +
|
||||
"&serverId=$frontHash"
|
||||
)
|
||||
?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
||||
) ?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
||||
|
||||
val sessionJoin = viaWebServer.requestSessionJoin(
|
||||
fromUndashed(profile.get("id")!!.asString),
|
||||
handler.data.backName!!,
|
||||
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())
|
||||
val id = profile.get("id")!!.asString
|
||||
mcLogger.info("Validated front-end session: ${handler.data.frontName} $id")
|
||||
callbackPlayerId.complete(id)
|
||||
} catch (e: Exception) {
|
||||
handler.disconnect("Online mode error: $e")
|
||||
callbackPlayerId.completeExceptionally(e)
|
||||
}
|
||||
handler.data.frontChannel.setAutoRead(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleLoginStart(handler: CloudMinecraftHandler, loginStart: LoginStart) {
|
||||
if (loginStart.username.length > 16) throw badLength
|
||||
|
||||
handler.data.frontName = loginStart.username
|
||||
handler.data.backName = handler.data.backName ?: handler.data.frontName
|
||||
|
||||
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) {
|
||||
@ -367,12 +400,12 @@ object LoginState : MinecraftConnectionState {
|
||||
}
|
||||
|
||||
object StatusState : MinecraftConnectionState {
|
||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
val i = msg.readerIndex()
|
||||
if (Type.VAR_INT.readPrimitive(msg) !in 0..1) throw IllegalArgumentException("Invalid packet id!")
|
||||
msg.readerIndex(i)
|
||||
forward(handler, msg.retainedSlice())
|
||||
msg.clear()
|
||||
override val state: State
|
||||
get() = State.STATUS
|
||||
|
||||
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||
if ((packet as UnknownPacket).id !in 0..1) throw IllegalArgumentException("Invalid packet id!")
|
||||
forward(handler, packet)
|
||||
}
|
||||
|
||||
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||
@ -394,12 +427,12 @@ object StatusState : MinecraftConnectionState {
|
||||
}
|
||||
|
||||
object PlayState : MinecraftConnectionState {
|
||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
val i = msg.readerIndex()
|
||||
if (Type.VAR_INT.readPrimitive(msg) !in 0..127) throw IllegalArgumentException("Invalid packet id!")
|
||||
msg.readerIndex(i)
|
||||
forward(handler, msg.retainedSlice())
|
||||
msg.clear()
|
||||
override val state: State
|
||||
get() = State.PLAY
|
||||
|
||||
override fun handlePacket(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, packet: Packet) {
|
||||
if ((packet as UnknownPacket).id !in 0..127) throw IllegalArgumentException("Invalid packet id!")
|
||||
forward(handler, packet)
|
||||
}
|
||||
|
||||
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) {
|
||||
val msg = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
PacketRegistry.encode(packet, msg, ProtocolVersion.getProtocol(handler.data.frontVer!!))
|
||||
forward(handler, msg.retain(), flush)
|
||||
val ch = handler.other!!
|
||||
if (flush) {
|
||||
ch.writeAndFlush(packet, ch.voidPromise())
|
||||
} else {
|
||||
ch.write(packet, ch.voidPromise())
|
||||
}
|
||||
} finally {
|
||||
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> {
|
||||
if (port == 25565) {
|
||||
try {
|
||||
@ -509,3 +537,7 @@ private fun matchAddress(addr: String, list: List<String>): Boolean {
|
||||
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
|
||||
*/
|
||||
interface Packet {
|
||||
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
|
||||
fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
|
||||
fun decode(byteBuf: ByteBuf, protocolVersion: Int)
|
||||
fun encode(byteBuf: ByteBuf, protocolVersion: Int)
|
||||
}
|
||||
|
||||
object PacketRegistry {
|
||||
@ -70,24 +70,24 @@ object PacketRegistry {
|
||||
)
|
||||
|
||||
fun getPacketConstructor(
|
||||
protocolVersion: ProtocolVersion,
|
||||
protocolVersion: Int,
|
||||
state: State,
|
||||
id: Int,
|
||||
serverBound: Boolean
|
||||
): Supplier<out Packet>? {
|
||||
return entries.firstOrNull {
|
||||
it.serverBound == serverBound && it.state == state
|
||||
&& it.versionRange.contains(protocolVersion.version) && it.id == id
|
||||
&& it.versionRange.contains(protocolVersion) && it.id == id
|
||||
}?.constructor
|
||||
}
|
||||
|
||||
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: ProtocolVersion): Int? {
|
||||
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: Int): Int? {
|
||||
return entries.firstOrNull {
|
||||
it.versionRange.contains(protocolVersion.version) && it.packetClass == packetClass
|
||||
it.versionRange.contains(protocolVersion) && it.packetClass == packetClass
|
||||
}?.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 packet =
|
||||
getPacketConstructor(protocolVersion, state, packetId, serverBound)?.get() ?: UnknownPacket(packetId)
|
||||
@ -96,7 +96,7 @@ object PacketRegistry {
|
||||
return packet
|
||||
}
|
||||
|
||||
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
val id = if (packet is UnknownPacket) {
|
||||
packet.id
|
||||
} else {
|
||||
@ -108,13 +108,13 @@ object PacketRegistry {
|
||||
}
|
||||
|
||||
class UnknownPacket(val id: Int) : Packet {
|
||||
lateinit var content: ByteBuf
|
||||
lateinit var content: ByteArray
|
||||
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
content = byteBuf.slice().clear()
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -127,14 +127,14 @@ class HandshakePacket : Packet {
|
||||
var port by Delegates.notNull<Int>()
|
||||
lateinit var nextState: State
|
||||
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
protocolId = Type.VAR_INT.readPrimitive(byteBuf)
|
||||
address = Type.STRING.read(byteBuf)
|
||||
port = byteBuf.readUnsignedShort()
|
||||
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.STRING.write(byteBuf, address)
|
||||
byteBuf.writeShort(port)
|
||||
@ -145,11 +145,11 @@ class HandshakePacket : Packet {
|
||||
class LoginStart : Packet {
|
||||
lateinit var username: String
|
||||
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
username = Type.STRING.read(byteBuf)
|
||||
}
|
||||
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
Type.STRING.write(byteBuf, username)
|
||||
}
|
||||
}
|
||||
@ -158,8 +158,8 @@ class CryptoResponse : Packet {
|
||||
lateinit var encryptedKey: ByteArray
|
||||
lateinit var encryptedToken: ByteArray
|
||||
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||
encryptedKey = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||
encryptedToken = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||
} else {
|
||||
@ -168,8 +168,8 @@ class CryptoResponse : Packet {
|
||||
}
|
||||
}
|
||||
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedKey)
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedToken)
|
||||
} else {
|
||||
@ -184,16 +184,16 @@ class CryptoResponse : Packet {
|
||||
class PluginResponse : Packet {
|
||||
var id by Delegates.notNull<Int>()
|
||||
var success by Delegates.notNull<Boolean>()
|
||||
lateinit var data: ByteBuf
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
lateinit var data: ByteArray
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
id = Type.VAR_INT.readPrimitive(byteBuf)
|
||||
success = byteBuf.readBoolean()
|
||||
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)
|
||||
byteBuf.writeBoolean(success)
|
||||
if (success) {
|
||||
@ -204,11 +204,11 @@ class PluginResponse : Packet {
|
||||
|
||||
class LoginDisconnect : Packet {
|
||||
lateinit var msg: String
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
msg = Type.STRING.read(byteBuf)
|
||||
}
|
||||
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
Type.STRING.write(byteBuf, msg)
|
||||
}
|
||||
}
|
||||
@ -218,9 +218,9 @@ class CryptoRequest : Packet {
|
||||
lateinit var publicKey: PublicKey
|
||||
lateinit var token: ByteArray
|
||||
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
serverId = Type.STRING.read(byteBuf)
|
||||
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||
if (protocolVersion >= ProtocolVersion.v1_8.version) {
|
||||
publicKey = KeyFactory.getInstance("RSA")
|
||||
.generatePublic(X509EncodedKeySpec(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)
|
||||
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, token)
|
||||
} else {
|
||||
@ -250,25 +250,25 @@ class LoginSuccess : Packet {
|
||||
lateinit var id: UUID
|
||||
lateinit var username: String
|
||||
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
id = when {
|
||||
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
|
||||
protocolVersion >= ProtocolVersion.v1_16.version -> {
|
||||
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))
|
||||
}
|
||||
else -> fromUndashed(Type.STRING.read(byteBuf))
|
||||
else -> parseUndashedId(Type.STRING.read(byteBuf))
|
||||
}
|
||||
username = Type.STRING.read(byteBuf)
|
||||
}
|
||||
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
when {
|
||||
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
|
||||
protocolVersion >= ProtocolVersion.v1_16.version -> {
|
||||
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())
|
||||
}
|
||||
else -> Type.STRING.write(byteBuf, id.toString().replace("-", ""))
|
||||
@ -279,11 +279,11 @@ class LoginSuccess : Packet {
|
||||
|
||||
class SetCompression : Packet {
|
||||
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)
|
||||
}
|
||||
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
override fun encode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
Type.VAR_INT.writePrimitive(byteBuf, threshold)
|
||||
}
|
||||
}
|
||||
@ -291,14 +291,14 @@ class SetCompression : Packet {
|
||||
class PluginRequest : Packet {
|
||||
var id by Delegates.notNull<Int>()
|
||||
lateinit var channel: String
|
||||
lateinit var data: ByteBuf
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||
lateinit var data: ByteArray
|
||||
override fun decode(byteBuf: ByteBuf, protocolVersion: Int) {
|
||||
id = Type.VAR_INT.readPrimitive(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.STRING.write(byteBuf, channel)
|
||||
byteBuf.writeBytes(data)
|
||||
|
@ -4,20 +4,15 @@ import com.google.common.base.Preconditions
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import io.ktor.application.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.cio.websocket.*
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.util.*
|
||||
import io.ktor.websocket.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
@ -61,7 +56,7 @@ class ViaWebApp {
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
webLogger.info("${call.request.local.remoteHost} (O: ${call.request.origin.remoteHost}) exception: $e")
|
||||
viaWebServer.onException(this, e)
|
||||
this.close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "INTERNAL ERROR"))
|
||||
} finally {
|
||||
@ -78,7 +73,7 @@ class ViaWebApp {
|
||||
}
|
||||
|
||||
// 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")
|
||||
return UUID(
|
||||
java.lang.Long.parseUnsignedLong(string.substring(0, 16), 16),
|
||||
@ -100,7 +95,7 @@ class WebDashboardServer {
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
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)
|
||||
|
||||
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" -> {
|
||||
val username = obj.getAsJsonPrimitive("username").asString
|
||||
val code = obj.getAsJsonPrimitive("code").asString
|
||||
val username = obj.get("username").asString
|
||||
val code = obj.get("code").asString
|
||||
|
||||
val check = httpClient.submitForm<JsonObject>(
|
||||
"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.ws.send(
|
||||
"""{"action": "minecraft_id_result", "success": true,
|
||||
"""{"action": "login_result", "success": true,
|
||||
| "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")
|
||||
} 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")
|
||||
}
|
||||
}
|
||||
|
@ -219,44 +219,48 @@ function renderActions() {
|
||||
actions.innerHTML = "";
|
||||
if (listenVisible) {
|
||||
if (username != null && mcauth_code != null) {
|
||||
let p = document.createElement("p");
|
||||
let add = document.createElement("a");
|
||||
p.appendChild(add);
|
||||
add.innerText = "Listen to " + username;
|
||||
add.href = "#";
|
||||
add.onclick = () => {
|
||||
addAction("Listen to " + username, () => {
|
||||
socket.send(JSON.stringify({
|
||||
"action": "minecraft_id_login",
|
||||
"username": username,
|
||||
"code": mcauth_code}));
|
||||
mcauth_code = null;
|
||||
};
|
||||
actions.appendChild(p);
|
||||
});
|
||||
}
|
||||
let p = document.createElement("p");
|
||||
let link = document.createElement("a");
|
||||
p.appendChild(link);
|
||||
link.innerText = "Listen to username in VIAaaS instance";
|
||||
link.href = "#";
|
||||
link.onclick = () => {
|
||||
let user = prompt("Username (Minecraft.ID is case-sensitive): ", "");
|
||||
addAction("Listen to premium login in VIAaaS instance", () => {
|
||||
let user = prompt("Premium username (case-sensitive): ", "");
|
||||
if (!user) return;
|
||||
let callbackUrl = new URL(location.origin + location.pathname + "#username=" + encodeURIComponent(user));
|
||||
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + "?callback=" + encodeURIComponent(callbackUrl);
|
||||
};
|
||||
actions.appendChild(p);
|
||||
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||
+ "?callback=" + encodeURIComponent(callbackUrl);
|
||||
});
|
||||
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) {
|
||||
console.log(event.data.toString());
|
||||
let parsed = JSON.parse(event.data);
|
||||
if (parsed.action == "ad_minecraft_id_login") {
|
||||
listenVisible = true;
|
||||
renderActions();
|
||||
} else if (parsed.action == "minecraft_id_result") {
|
||||
} else if (parsed.action == "login_result") {
|
||||
if (!parsed.success) {
|
||||
alert("VIAaaS instance couldn't verify account via Minecraft.ID");
|
||||
alert("VIAaaS instance couldn't verify Minecraft account");
|
||||
} else {
|
||||
listen(parsed.token);
|
||||
saveToken(parsed.token);
|
||||
|
Loading…
Reference in New Issue
Block a user