Archived
1
0
Fork 0
This repository has been archived on 2025-03-02. You can view files and clone it, but cannot push or open issues or pull requests.
uranos/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt
Moritz Ruth ffe2349884
Update everything to 1.16.4
this took me about 12 hours. it's 1 am
2020-12-21 01:05:28 +01:00

205 lines
7.6 KiB
Kotlin

package space.blokk.net
import io.netty.buffer.Unpooled
import space.blokk.BlokkServer
import space.blokk.chat.TextComponent
import space.blokk.event.ifCancelled
import space.blokk.net.MinecraftProtocolDataTypes.writeString
import space.blokk.net.event.PlayerInitializationEvent
import space.blokk.net.event.SessionAfterLoginEvent
import space.blokk.net.packet.login.*
import space.blokk.net.packet.play.*
import space.blokk.player.BlokkPlayer
import space.blokk.tag.TagRegistry
import space.blokk.util.*
import space.blokk.world.Chunk
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
class LoginAndJoinProcedure(val session: BlokkSession) {
private val tagsPacket by lazy { TagsPacket(TagRegistry.tags) }
suspend fun start(packet: LoginStartPacket) {
session.state.getOrFail<Session.State.WaitingForLoginStart>()
if (session.server.config.authenticateAndEncrypt) {
val verifyToken = EncryptionUtils.generateVerifyToken()
session.state = Session.State.WaitingForEncryptionVerification(packet.username, verifyToken)
session.send(EncryptionRequestPacket(session.server.x509EncodedPublicKey, verifyToken))
} else {
val uuid = UUID.nameUUIDFromBytes("OfflinePlayer:${packet.username}".toByteArray())
session.send(LoginSuccessPacket(uuid, packet.username))
session.state = Session.State.LoginSucceeded(packet.username, uuid)
afterLogin()
}
}
suspend fun onEncryptionResponse(packet: EncryptionResponsePacket) {
val state: Session.State.WaitingForEncryptionVerification = session.state.getOrFail()
val cipher: Cipher = Cipher.getInstance("RSA")
cipher.init(Cipher.DECRYPT_MODE, session.server.keyPair.private)
// Decrypt verify token
val verifyToken: ByteArray = cipher.doFinal(packet.verifyToken)
if (!verifyToken.contentEquals(state.verifyToken)) {
session.disconnect(TextComponent of "Encryption failed.")
return
}
// Decrypt shared secret
val sharedSecretKey = SecretKeySpec(cipher.doFinal(packet.sharedSecret), "AES")
session.state = Session.State.Encrypted(state.username)
session.enableEncryptionCodec(sharedSecretKey)
val hash = MessageDigest.getInstance("SHA-1")
hash.update(sharedSecretKey.encoded)
hash.update(session.server.x509EncodedPublicKey)
val hashString = EncryptionUtils.toMinecraftStyleSha1String(hash.digest())
val result = AuthenticationHelper.authenticate(hashString, state.username)
session.send(SetCompressionPacket(session.server.config.packetCompressionThreshold))
session.enableCompressionCodec()
session.send(LoginSuccessPacket(result.uuid, result.username))
session.state = Session.State.LoginSucceeded(result.username, result.uuid)
afterLogin()
}
private suspend fun afterLogin() {
val state: Session.State.LoginSucceeded = session.state.getOrFail()
val event = session.emit(SessionAfterLoginEvent(session))
val initialWorldAndLocation = event.initialWorldAndLocation
when {
event.cancelled -> session.disconnect()
initialWorldAndLocation == null -> {
session.logger warn "Could not join because no spawn location was set"
session.disconnect(loggableReason = "No spawn location set")
}
else -> {
// TODO: Spawn the player entity
session.send(
JoinGamePacket(
0,
event.gameMode,
event.hardcoreHearts,
initialWorldAndLocation.world,
event.maxViewDistance,
event.reducedDebugInfo,
event.respawnScreenEnabled
)
)
session.sendPluginMessage(
"minecraft:brand",
Unpooled.buffer().writeString("Blokk ${BlokkServer.VERSION_WITH_V}")
)
//
// // As this is only visual, there is no way of changing it aside from intercepting the packet.
// session.send(ServerDifficultyPacket(DifficultySettings(Difficulty.NORMAL, false)))
// session.send(
// PlayerAbilitiesPacket(
// event.invulnerable,
// event.flying,
// event.canFly,
// // TODO: Consider allowing to modify this value
// event.gameMode == GameMode.CREATIVE,
// // TODO: Find out how this relates to the entity property named `generic.flying_speed`
// event.flyingSpeed,
// event.fieldOfView
// )
// )
session.state = Session.State.WaitingForClientSettings(
state.username,
state.uuid,
event.flying,
event.canFly,
event.flyingSpeed,
event.fieldOfView,
event.gameMode,
initialWorldAndLocation,
event.invulnerable,
event.reducedDebugInfo,
event.selectedHotbarSlot
)
}
}
}
suspend fun onClientSettingsReceived(packet: ClientSettingsPacket) {
val state: Session.State.WaitingForClientSettings = session.state.getOrFail()
val settings = packet.asPlayerSettings()
session.emit(PlayerInitializationEvent(session, settings)).ifCancelled {
session.disconnect(loggableReason = "PlayerInitializationEvent was cancelled")
return
}
val player = BlokkPlayer(
session,
state.username,
state.uuid,
state.gameMode,
settings,
state.initialWorldAndLocation.world,
state.initialWorldAndLocation.location,
state.reducedDebugInfo,
state.fieldOfView,
state.canFly,
state.flying,
state.flyingSpeed,
state.invulnerable,
state.selectedHotbarSlot
)
session.state = Session.State.Joining(player)
session.send(SetSelectedHotbarSlotPacket(state.selectedHotbarSlot))
session.send(DeclareRecipesPacket(session.server.recipeRegistry.items.values))
session.send(tagsPacket)
// DeclareCommands
// UnlockRecipes
session.send(PlayerPositionAndLookPacket(state.initialWorldAndLocation.location))
session.send(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
it.name,
it.gameMode,
it.rtt,
it.playerListName,
emptyMap() // TODO: Load skin
)
}.toMap())))
session.send(
PlayerInfoPacket(
PlayerInfoPacket.Action.UpdateLatency(session.server.players.map { it.uuid to it.rtt }.toMap())
)
)
session.send(UpdateViewPositionPacket(Chunk.Key.from(player.location.asVoxelLocation())))
player.sendChunksAndLight()
// WorldBorder
// TODO: Send SpawnPosition packet
session.send(PlayerPositionAndLookPacket(state.initialWorldAndLocation.location))
// TODO: Wait for ClientStatus(action=0) packet
}
}