Move packet encoders and decoders to their own project
This commit is contained in:
parent
8403140a70
commit
b021a4b753
94 changed files with 733 additions and 727 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
|||
/.gradle/
|
||||
/build/
|
||||
/blokk-api/build/
|
||||
/blokk-server/build/
|
||||
build/
|
||||
!src/**/build
|
||||
|
||||
/data/
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Blokk
|
||||
## To Do
|
||||
- Move packets to their own project
|
||||
- Move packet encoders and decoders to their own project
|
||||
- Stop Using Spek
|
||||
- Support packet compression
|
||||
|
|
|
@ -3,7 +3,7 @@ package space.blokk.chat
|
|||
/**
|
||||
* Legacy formatting codes. You should use [ChatComponent][space.blokk.chat.ChatComponent] whenever it's possible,
|
||||
* but sometimes these codes are required, for example in
|
||||
* [the name of a player in a server list sample][space.blokk.net.protocols.status.ResponsePacket.Players.SampleEntry.name].
|
||||
* [the name of a player in a server list sample][space.blokk.net.packets.status.ResponsePacket.Players.SampleEntry.name].
|
||||
*/
|
||||
enum class FormattingCode(private val char: Char) {
|
||||
BLACK('0'),
|
||||
|
|
|
@ -4,8 +4,8 @@ import io.netty.buffer.ByteBuf
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import space.blokk.events.EventTarget
|
||||
import space.blokk.net.events.SessionEvent
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.Protocol
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
import space.blokk.net.packets.Protocol
|
||||
import java.net.InetAddress
|
||||
|
||||
interface Session : EventTarget<SessionEvent> {
|
||||
|
|
|
@ -2,7 +2,7 @@ package space.blokk.net.events
|
|||
|
||||
import space.blokk.events.Cancellable
|
||||
import space.blokk.net.Session
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Emitted when a packet is received. **You should only listen to this event when there is no other option.**
|
||||
|
|
|
@ -2,7 +2,7 @@ package space.blokk.net.events
|
|||
|
||||
import space.blokk.events.Cancellable
|
||||
import space.blokk.net.Session
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Emitted when a packet is going to be sent. **You should only listen to this event when there is no other option.**
|
||||
|
|
|
@ -2,7 +2,7 @@ package space.blokk.net.events
|
|||
|
||||
import space.blokk.events.Cancellable
|
||||
import space.blokk.net.Session
|
||||
import space.blokk.net.protocols.status.ResponsePacket
|
||||
import space.blokk.net.packets.status.ResponsePacket
|
||||
|
||||
/**
|
||||
* Emitted when a client requests general info about the server, most likely to show it in the server list.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package space.blokk.net.packets
|
||||
|
||||
abstract class Packet {
|
||||
override fun toString(): String = this::class.java.simpleName + "(no data)"
|
||||
}
|
||||
|
||||
abstract class IncomingPacket : Packet()
|
||||
abstract class OutgoingPacket : Packet()
|
|
@ -0,0 +1,14 @@
|
|||
package space.blokk.net.packets
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
abstract class PacketCodec<T : Packet>(val id: Int, val dataType: KClass<T>)
|
||||
|
||||
abstract class IncomingPacketCodec<T : IncomingPacket>(id: Int, dataType: KClass<T>) : PacketCodec<T>(id, dataType) {
|
||||
abstract fun decode(msg: ByteBuf): T
|
||||
}
|
||||
|
||||
abstract class OutgoingPacketCodec<T : OutgoingPacket>(id: Int, dataType: KClass<T>) : PacketCodec<T>(id, dataType) {
|
||||
abstract fun T.encode(dst: ByteBuf)
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package space.blokk.net.packets
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
abstract class Protocol constructor(val name: String, vararg codecs: PacketCodec<*>) {
|
||||
val test = emptyList<Packet>()
|
||||
private val codecsByPacketType = codecs.map { it.dataType to it }.toMap()
|
||||
val incomingPacketCodecsByID = codecs.filterIsInstance<IncomingPacketCodec<*>>().map { it.id to it }.toMap()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Packet> getCodecByType(type: KClass<T>): PacketCodec<T> = codecsByPacketType[type] as PacketCodec<T>?
|
||||
?: throw Exception(
|
||||
"No packet companion found for this packet type. " +
|
||||
"This can happen if the packet is not part of the current protocol."
|
||||
)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package space.blokk.net.packets.handshaking
|
||||
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* This is the first packet a client should send. Per default, the server immediately switches
|
||||
* the [session's currentProtocol][space.blokk.net.Session.currentProtocol] to
|
||||
* `STATUS` or `LOGIN`,
|
||||
* depending on the value of [loginAttempt].
|
||||
*
|
||||
* @param protocolVersion The number of the [protocol version](https://wiki.vg/Protocol_version_numbers) used by the client.
|
||||
* @param serverAddress Hostname or IP address that was used to connect.
|
||||
* @param serverPort Port that was used to connect.
|
||||
* @param loginAttempt Whether the server should use the `STATUS` or
|
||||
* the `LOGIN` protocol from now on.
|
||||
*/
|
||||
data class HandshakePacket(
|
||||
val protocolVersion: Int,
|
||||
val serverAddress: String,
|
||||
val serverPort: Int,
|
||||
val loginAttempt: Boolean
|
||||
) : IncomingPacket()
|
|
@ -0,0 +1,11 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.chat.ChatComponent
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Sent by the server to cancel an ongoing login process.
|
||||
*
|
||||
* @param reason The reason why the login process was cancelled.
|
||||
*/
|
||||
data class DisconnectPacket(val reason: ChatComponent) : OutgoingPacket()
|
|
@ -1,10 +1,6 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Sent by the server to request authentication and encryption from the client. The client must respond with
|
||||
|
@ -16,16 +12,6 @@ data class EncryptionRequestPacket(
|
|||
val publicKey: ByteArray,
|
||||
val verifyToken: ByteArray
|
||||
) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<EncryptionRequestPacket>(0x01, EncryptionRequestPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeString("") // Required for legacy reasons
|
||||
dst.writeVarInt(publicKey.size)
|
||||
dst.writeBytes(publicKey)
|
||||
dst.writeVarInt(verifyToken.size)
|
||||
dst.writeBytes(verifyToken)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
|
@ -0,0 +1,31 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Sent by the client in response to an [EncryptionRequestPacket].
|
||||
*
|
||||
* @see <a href="https://wiki.vg/Protocol_Encryption">https://wiki.vg/Protocol_Encryption</a>
|
||||
*/
|
||||
data class EncryptionResponsePacket(
|
||||
val sharedSecret: ByteArray,
|
||||
val verifyToken: ByteArray
|
||||
) : IncomingPacket() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as EncryptionResponsePacket
|
||||
|
||||
if (!sharedSecret.contentEquals(other.sharedSecret)) return false
|
||||
if (!verifyToken.contentEquals(other.verifyToken)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = sharedSecret.contentHashCode()
|
||||
result = 31 * result + verifyToken.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -1,10 +1,6 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Optionally sent by the server to transfer additional, non-standard information required for the login process.
|
||||
|
@ -19,14 +15,6 @@ data class LoginPluginRequestPacket(
|
|||
val channel: String,
|
||||
val data: ByteArray
|
||||
) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<LoginPluginRequestPacket>(0x04, LoginPluginRequestPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(messageID)
|
||||
dst.writeString(channel)
|
||||
dst.writeBytes(data)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
|
@ -1,9 +1,6 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Sent by the client in response to a [LoginPluginRequestPacket].
|
||||
|
@ -14,22 +11,6 @@ data class LoginPluginResponsePacket(
|
|||
val messageID: Int,
|
||||
val data: ByteArray?
|
||||
) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<LoginPluginResponsePacket>(0x02, LoginPluginResponsePacket::class) {
|
||||
override fun decode(msg: ByteBuf): LoginPluginResponsePacket {
|
||||
return with(MinecraftDataTypes) {
|
||||
val messageID = msg.readVarInt()
|
||||
val successful = msg.readBoolean()
|
||||
|
||||
// TODO: Test this
|
||||
val data =
|
||||
if (successful) ByteArray(msg.readableBytes()).also { msg.readBytes(it) }
|
||||
else null
|
||||
|
||||
LoginPluginResponsePacket(messageID, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
|
@ -0,0 +1,14 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Sent by the client to initialize the login process.
|
||||
*
|
||||
* @param username The username of the client. This is not validated (yet). Can only be 16 characters long.
|
||||
*/
|
||||
data class LoginStartPacket(val username: String) : IncomingPacket() {
|
||||
init {
|
||||
if (username.length > 16) throw IllegalArgumentException("username can only be 16 characters long")
|
||||
}
|
||||
}
|
|
@ -1,28 +1,18 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Sent by the server to indicate that the login process succeeded.
|
||||
* After this packet is sent, the [session's currentProtocol][space.blokk.net.Session.currentProtocol] will be switched to
|
||||
* [PLAY][space.blokk.net.protocols.play.PlayProtocol].
|
||||
* `PLAY`.
|
||||
*
|
||||
* @param uuid The UUID of the player.
|
||||
* @param username The username of the player. Can only be 16 characters long.
|
||||
*/
|
||||
data class LoginSuccessPacket(val uuid: UUID, val username: String) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<LoginSuccessPacket>(0x02, LoginSuccessPacket::class)
|
||||
|
||||
init {
|
||||
if (username.length > 16) throw IllegalArgumentException("username can only be 16 characters long")
|
||||
}
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeString(uuid.toString())
|
||||
dst.writeString(username)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Sent by the server to indicate that all following packets are compressed and the client should also compress its
|
||||
* packets.
|
||||
*
|
||||
* @param threshold Maximum size before a packet is compressed. Values lower than 1 will disable compression.
|
||||
*/
|
||||
data class SetCompressionPacket(val threshold: Int) : OutgoingPacket()
|
|
@ -0,0 +1,18 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
import space.blokk.players.ChatMode
|
||||
import space.blokk.players.DisplayedSkinParts
|
||||
import space.blokk.players.MainHand
|
||||
|
||||
/**
|
||||
* Sent by the client when the player joins or the settings changed.
|
||||
*/
|
||||
data class ClientSettingsPacket(
|
||||
val locale: String,
|
||||
val viewDistance: Byte,
|
||||
val chatMode: ChatMode,
|
||||
val chatColorsEnabled: Boolean,
|
||||
val displayedSkinParts: DisplayedSkinParts,
|
||||
val mainHand: MainHand
|
||||
) : IncomingPacket()
|
|
@ -1,9 +1,6 @@
|
|||
package space.blokk.net.protocols.play
|
||||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Can be used by client-side mods to communicate with a potentially modded server.
|
||||
|
@ -13,19 +10,6 @@ import space.blokk.net.protocols.IncomingPacketCompanion
|
|||
* @param data Data which was sent.
|
||||
*/
|
||||
data class IncomingPluginMessagePacket(val channel: String, val data: ByteArray) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<IncomingPluginMessagePacket>(0x0B, IncomingPluginMessagePacket::class) {
|
||||
override fun decode(msg: ByteBuf): IncomingPluginMessagePacket {
|
||||
return with(MinecraftDataTypes) {
|
||||
val channel = msg.readString()
|
||||
|
||||
// TODO: Test this
|
||||
val data = ByteArray(msg.readableBytes()).also { msg.readBytes(it) }
|
||||
|
||||
IncomingPluginMessagePacket(channel, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
|
@ -0,0 +1,35 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
import space.blokk.players.GameMode
|
||||
import space.blokk.worlds.WorldDimension
|
||||
import space.blokk.worlds.WorldType
|
||||
|
||||
/**
|
||||
* Sent by the server after the client logged in.
|
||||
*
|
||||
* @param entityID ID of the player entity.
|
||||
* @param gameMode Game mode of the player.
|
||||
* @param hardcore Whether the player is in hardcore mode.
|
||||
* @param worldDimension Dimension of the world the player joins.
|
||||
* @param worldSeedHash First 8 bytes of the SHA-256 hash of the world's seed.
|
||||
* @param worldType Type of the world the player joins.
|
||||
* @param viewDistance Maximum view distance.
|
||||
* @param reducedDebugInfo Whether the debug screen shows only reduced info.
|
||||
* @param respawnScreenEnabled Whether the respawn screen is shown when the player dies.
|
||||
*/
|
||||
data class JoinGamePacket(
|
||||
val entityID: Int,
|
||||
val gameMode: GameMode,
|
||||
val hardcore: Boolean,
|
||||
val worldDimension: WorldDimension,
|
||||
val worldSeedHash: Long,
|
||||
val worldType: WorldType,
|
||||
val viewDistance: Byte,
|
||||
val reducedDebugInfo: Boolean,
|
||||
val respawnScreenEnabled: Boolean
|
||||
) : OutgoingPacket() {
|
||||
init {
|
||||
if (viewDistance < 2 || viewDistance > 32) throw IllegalArgumentException("viewDistance must be between 2 and 32.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Can be used by mods or plugins to communicate with a potentially modded client.
|
||||
* Minecraft itself [also uses this for some things](https://wiki.vg/Plugin_channels#Channels_internal_to_Minecraft).
|
||||
*
|
||||
* @param channel Channel in which the message should be send.
|
||||
* @param data Data which should be send. The ByteBuf needs to be released by the caller.
|
||||
*/
|
||||
data class OutgoingPluginMessagePacket(val channel: String, val data: ByteBuf) : OutgoingPacket()
|
|
@ -1,8 +1,6 @@
|
|||
package space.blokk.net.protocols.play
|
||||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Tells the client some things about the player.
|
||||
|
@ -21,18 +19,4 @@ data class PlayerAbilitiesPacket(
|
|||
val instantlyBreakBlocks: Boolean,
|
||||
val flyingSpeed: Float = 0.5f,
|
||||
val fieldOfView: Float = 0.1f
|
||||
) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<PlayerAbilitiesPacket>(0x32, PlayerAbilitiesPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
var flags = 0
|
||||
if (invulnerable) flags = flags and 0x01
|
||||
if (flying) flags = flags and 0x02
|
||||
if (flyingAllowed) flags = flags and 0x04
|
||||
if (instantlyBreakBlocks) flags = flags and 0x08
|
||||
|
||||
dst.writeByte(flags)
|
||||
dst.writeFloat(flyingSpeed)
|
||||
dst.writeFloat(fieldOfView)
|
||||
}
|
||||
}
|
||||
) : OutgoingPacket()
|
|
@ -0,0 +1,9 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
import space.blokk.worlds.WorldDifficulty
|
||||
|
||||
/**
|
||||
* Sets the difficulty shown in the pause menu.
|
||||
*/
|
||||
data class ServerDifficultyPacket(val difficulty: WorldDifficulty, val locked: Boolean) : OutgoingPacket()
|
|
@ -0,0 +1,10 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Sent by the client to determine the RTT of the connection. The server responds with a [PongPacket].
|
||||
*
|
||||
* @param payload A random value generated by the client. The [PongPacket] sent in response must use the same payload.
|
||||
*/
|
||||
data class PingPacket(val payload: Long) : IncomingPacket()
|
|
@ -0,0 +1,10 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Sent by the server in response to a [PingPacket].
|
||||
*
|
||||
* @param payload A random value generated by the client. This must be the same as in the corresponding [PingPacket].
|
||||
*/
|
||||
data class PongPacket(val payload: Long) : OutgoingPacket()
|
|
@ -0,0 +1,12 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
|
||||
/**
|
||||
* Sent by the client to request general information about the server.
|
||||
*
|
||||
* The server responds with a [ResponsePacket].
|
||||
*
|
||||
* @see [space.blokk.net.events.ServerListInfoRequestEvent]
|
||||
*/
|
||||
class RequestPacket : IncomingPacket()
|
|
@ -1,13 +1,8 @@
|
|||
package space.blokk.net.protocols.status
|
||||
package space.blokk.net.packets.status
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.Blokk
|
||||
import space.blokk.chat.TextComponent
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.utils.toJson
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Sent by the server in response to a [RequestPacket].
|
||||
|
@ -26,27 +21,11 @@ data class ResponsePacket(
|
|||
val players: Players,
|
||||
val favicon: String? = null
|
||||
) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<ResponsePacket>(0x00, ResponsePacket::class)
|
||||
|
||||
init {
|
||||
if (description.getExtraTypes().find { it != TextComponent::class } != null)
|
||||
throw Exception("description may only contain instances of TextComponent")
|
||||
}
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
with(MinecraftDataTypes) {
|
||||
dst.writeString(Blokk.json.toJson(mapOf(
|
||||
"version" to mapOf(
|
||||
"name" to versionName,
|
||||
"protocol" to protocolVersion
|
||||
),
|
||||
"players" to players,
|
||||
"description" to description,
|
||||
"favicon" to favicon?.let { "data:image/png;base64,$it" }
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Players(val max: Int, val online: Int, val sample: List<SampleEntry>) {
|
||||
/**
|
|
@ -1,29 +0,0 @@
|
|||
package space.blokk.net.protocols
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
abstract class Packet {
|
||||
override fun toString(): String = this::class.java.simpleName + "(no data)"
|
||||
}
|
||||
|
||||
abstract class IncomingPacket : Packet()
|
||||
|
||||
abstract class OutgoingPacket : Packet() {
|
||||
abstract fun encode(dst: ByteBuf)
|
||||
}
|
||||
|
||||
sealed class PacketCompanion<T : Packet>(val id: Int, val packetType: KClass<T>)
|
||||
|
||||
abstract class IncomingPacketCompanion<T : IncomingPacket>(
|
||||
id: Int,
|
||||
packetType: KClass<T>
|
||||
) : PacketCompanion<T>(id, packetType) {
|
||||
abstract fun decode(msg: ByteBuf): T
|
||||
}
|
||||
|
||||
abstract class OutgoingPacketCompanion<T : OutgoingPacket>(
|
||||
id: Int,
|
||||
packetType: KClass<T>
|
||||
) : PacketCompanion<T>(id, packetType)
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package space.blokk.net.protocols
|
||||
|
||||
abstract class Protocol internal constructor(val name: String, vararg packets: PacketCompanion<*>) {
|
||||
val incomingPackets = packets.filterIsInstance<IncomingPacketCompanion<*>>()
|
||||
val incomingPacketsByID = incomingPackets.mapToIDMap()
|
||||
val outgoingPackets = packets.filterIsInstance<OutgoingPacketCompanion<*>>()
|
||||
val outgoingPacketsByID = outgoingPackets.mapToIDMap()
|
||||
val packetCompanionsByPacketType = packets.map { it.packetType to it }.toMap()
|
||||
|
||||
private fun <T : PacketCompanion<*>> Iterable<T>.mapToIDMap() = map { it.id to it }.toMap()
|
||||
|
||||
fun validate() {
|
||||
ensureDistinctIDs(incomingPackets, "serverbound")
|
||||
ensureDistinctIDs(outgoingPackets, "clientbound")
|
||||
}
|
||||
|
||||
override fun toString() = name
|
||||
|
||||
private fun ensureDistinctIDs(packets: Iterable<PacketCompanion<*>>, type: String) {
|
||||
packets.groupBy { it.id }.forEach { (id, p) ->
|
||||
if (p.count() > 1) {
|
||||
val packetsString = p.joinToString(", ", limit = 4) { it.packetType.simpleName.toString() }
|
||||
throw Error("Multiple $type packets use the same ID (0x${id.toString(16)}): $packetsString")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package space.blokk.net.protocols
|
||||
|
||||
import space.blokk.net.protocols.handshaking.HandshakingProtocol
|
||||
import space.blokk.net.protocols.login.LoginProtocol
|
||||
import space.blokk.net.protocols.status.StatusProtocol
|
||||
|
||||
object Protocols {
|
||||
val all = setOf(HandshakingProtocol, StatusProtocol, LoginProtocol)
|
||||
fun validate() = all.forEach(Protocol::validate)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package space.blokk.net.protocols.handshaking
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
|
||||
/**
|
||||
* This is the first packet a client should send. Per default, the server immediately switches
|
||||
* the [session's currentProtocol][space.blokk.net.Session.currentProtocol] to
|
||||
* [STATUS][space.blokk.net.protocols.status.StatusProtocol] or [LOGIN][space.blokk.net.protocols.login.LoginProtocol],
|
||||
* depending on the value of [loginAttempt].
|
||||
*
|
||||
* @param protocolVersion The number of the [protocol version](https://wiki.vg/Protocol_version_numbers) used by the client.
|
||||
* @param serverAddress Hostname or IP address that was used to connect.
|
||||
* @param serverPort Port that was used to connect.
|
||||
* @param loginAttempt Whether the server should use the [STATUS][space.blokk.net.protocols.status.StatusProtocol] or
|
||||
* the [LOGIN][space.blokk.net.protocols.login.LoginProtocol] protocol from now on.
|
||||
*/
|
||||
data class HandshakePacket(
|
||||
val protocolVersion: Int,
|
||||
val serverAddress: String,
|
||||
val serverPort: Int,
|
||||
val loginAttempt: Boolean
|
||||
) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<HandshakePacket>(0x00, HandshakePacket::class) {
|
||||
override fun decode(msg: ByteBuf): HandshakePacket {
|
||||
return with(MinecraftDataTypes) {
|
||||
HandshakePacket(
|
||||
protocolVersion = msg.readVarInt(),
|
||||
serverAddress = msg.readString(),
|
||||
serverPort = msg.readUnsignedShort(),
|
||||
loginAttempt = msg.readVarInt() == 2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package space.blokk.net.protocols.handshaking
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object HandshakingProtocol : Protocol("HANDSHAKING", HandshakePacket)
|
|
@ -1,20 +0,0 @@
|
|||
package space.blokk.net.protocols.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.chat.ChatComponent
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the server to cancel an ongoing login process.
|
||||
*
|
||||
* @param reason The reason why the login process was cancelled.
|
||||
*/
|
||||
data class DisconnectPacket(val reason: ChatComponent) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<DisconnectPacket>(0x00, DisconnectPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeString(reason.toJson())
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package space.blokk.net.protocols.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the client in response to an [EncryptionRequestPacket].
|
||||
*
|
||||
* @see <a href="https://wiki.vg/Protocol_Encryption">https://wiki.vg/Protocol_Encryption</a>
|
||||
*/
|
||||
data class EncryptionResponsePacket(
|
||||
val sharedSecret: ByteArray,
|
||||
val verifyToken: ByteArray
|
||||
) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<EncryptionResponsePacket>(0x01, EncryptionResponsePacket::class) {
|
||||
override fun decode(msg: ByteBuf): EncryptionResponsePacket {
|
||||
return with(MinecraftDataTypes) {
|
||||
val sharedSecretLength = msg.readVarInt()
|
||||
val sharedSecret = ByteArray(sharedSecretLength)
|
||||
msg.readBytes(sharedSecret)
|
||||
|
||||
val verifyTokenLength = msg.readVarInt()
|
||||
val verifyToken = ByteArray(verifyTokenLength)
|
||||
msg.readBytes(verifyToken)
|
||||
|
||||
EncryptionResponsePacket(sharedSecret, verifyToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as EncryptionResponsePacket
|
||||
|
||||
if (!sharedSecret.contentEquals(other.sharedSecret)) return false
|
||||
if (!verifyToken.contentEquals(other.verifyToken)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = sharedSecret.contentHashCode()
|
||||
result = 31 * result + verifyToken.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package space.blokk.net.protocols.login
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object LoginProtocol : Protocol(
|
||||
"LOGIN",
|
||||
DisconnectPacket,
|
||||
LoginStartPacket,
|
||||
EncryptionRequestPacket,
|
||||
EncryptionResponsePacket,
|
||||
SetCompressionPacket,
|
||||
LoginSuccessPacket,
|
||||
LoginPluginRequestPacket,
|
||||
LoginPluginResponsePacket
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
package space.blokk.net.protocols.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the client to initialize the login process.
|
||||
*
|
||||
* @param username The username of the client. This is not validated (yet). Can only be 16 characters long.
|
||||
*/
|
||||
data class LoginStartPacket(val username: String) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<LoginStartPacket>(0x00, LoginStartPacket::class) {
|
||||
override fun decode(msg: ByteBuf): LoginStartPacket {
|
||||
return with(MinecraftDataTypes) { LoginStartPacket(msg.readString()) }
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (username.length > 16) throw IllegalArgumentException("username can only be 16 characters long")
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package space.blokk.net.protocols.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the server to indicate that all following packets are compressed and the client should also compress its
|
||||
* packets.
|
||||
*
|
||||
* @param threshold Maximum size before a packet is compressed. Values lower than 1 will disable compression.
|
||||
*/
|
||||
data class SetCompressionPacket(val threshold: Int) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<SetCompressionPacket>(0x03, SetCompressionPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(threshold)
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package space.blokk.net.protocols.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes
|
||||
import space.blokk.net.PacketDecodingException
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
import space.blokk.players.ChatMode
|
||||
import space.blokk.players.DisplayedSkinParts
|
||||
import space.blokk.players.MainHand
|
||||
import space.blokk.utils.checkBit
|
||||
|
||||
/**
|
||||
* Sent by the client when the player joins or the settings changed.
|
||||
*/
|
||||
data class ClientSettingsPacket(
|
||||
val locale: String,
|
||||
val viewDistance: Byte,
|
||||
val chatMode: ChatMode,
|
||||
val chatColorsEnabled: Boolean,
|
||||
val displayedSkinParts: DisplayedSkinParts,
|
||||
val mainHand: MainHand
|
||||
) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<ClientSettingsPacket>(0x05, ClientSettingsPacket::class) {
|
||||
override fun decode(msg: ByteBuf): ClientSettingsPacket {
|
||||
return with(MinecraftDataTypes) {
|
||||
val locale = msg.readString()
|
||||
val viewDistance = msg.readByte()
|
||||
|
||||
val chatMode = when (msg.readVarInt()) {
|
||||
0 -> ChatMode.ENABLED
|
||||
1 -> ChatMode.COMMANDS_ONLY
|
||||
2 -> ChatMode.HIDDEN
|
||||
else -> throw PacketDecodingException()
|
||||
}
|
||||
|
||||
val chatColorsEnabled = msg.readBoolean()
|
||||
|
||||
val skinFlags = msg.readByte()
|
||||
val displayedSkinParts = DisplayedSkinParts(
|
||||
cape = skinFlags.checkBit(0),
|
||||
jacket = skinFlags.checkBit(1),
|
||||
leftSleeve = skinFlags.checkBit(2),
|
||||
rightSleeve = skinFlags.checkBit(3),
|
||||
leftPantsLeg = skinFlags.checkBit(4),
|
||||
rightPantsLeg = skinFlags.checkBit(5),
|
||||
hat = skinFlags.checkBit(6)
|
||||
)
|
||||
|
||||
val mainHand = when (msg.readVarInt()) {
|
||||
0 -> MainHand.LEFT
|
||||
1 -> MainHand.RIGHT
|
||||
else -> throw PacketDecodingException()
|
||||
}
|
||||
|
||||
ClientSettingsPacket(
|
||||
locale,
|
||||
viewDistance,
|
||||
chatMode,
|
||||
chatColorsEnabled,
|
||||
displayedSkinParts,
|
||||
mainHand
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package space.blokk.net.protocols.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
|
||||
/**
|
||||
* Can be used by mods or plugins to communicate with a potentially modded client.
|
||||
* Minecraft itself [also uses this for some things](https://wiki.vg/Plugin_channels#Channels_internal_to_Minecraft).
|
||||
*
|
||||
* @param channel Channel in which the message should be send.
|
||||
* @param data Data which should be send. The ByteBuf needs to be released by the caller.
|
||||
*/
|
||||
data class OutgoingPluginMessagePacket(val channel: String, val data: ByteBuf) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<OutgoingPluginMessagePacket>(0x19, OutgoingPluginMessagePacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeString(channel)
|
||||
dst.writeBytes(data)
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package space.blokk.net.protocols.play
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object PlayProtocol : Protocol(
|
||||
"PLAY",
|
||||
JoinGamePacket,
|
||||
ClientSettingsPacket,
|
||||
IncomingPluginMessagePacket,
|
||||
OutgoingPluginMessagePacket,
|
||||
PlayerAbilitiesPacket,
|
||||
ServerDifficultyPacket
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
package space.blokk.net.protocols.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.worlds.WorldDifficulty
|
||||
|
||||
/**
|
||||
* Sets the difficulty shown in the pause menu.
|
||||
*/
|
||||
data class ServerDifficultyPacket(val difficulty: WorldDifficulty, val locked: Boolean) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<ServerDifficultyPacket>(0x0E, ServerDifficultyPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeByte(
|
||||
when (difficulty) {
|
||||
WorldDifficulty.PEACEFUL -> 0
|
||||
WorldDifficulty.EASY -> 1
|
||||
WorldDifficulty.NORMAL -> 2
|
||||
WorldDifficulty.HARD -> 3
|
||||
}
|
||||
)
|
||||
|
||||
dst.writeBoolean(locked)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package space.blokk.net.protocols.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the client to determine the RTT of the connection. The server responds with a [PongPacket].
|
||||
*
|
||||
* @param payload A random value generated by the client. The [PongPacket] sent in response must use the same payload.
|
||||
*/
|
||||
data class PingPacket(val payload: Long) : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<PingPacket>(0x01, PingPacket::class) {
|
||||
override fun decode(msg: ByteBuf): PingPacket {
|
||||
return PingPacket(msg.readLong())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package space.blokk.net.protocols.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the server in response to a [PingPacket].
|
||||
*
|
||||
* @param payload A random value generated by the client. This must be the same as in the corresponding [PingPacket].
|
||||
*/
|
||||
data class PongPacket(val payload: Long) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<PongPacket>(0x01, PongPacket::class)
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
dst.writeLong(payload)
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package space.blokk.net.protocols.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
|
||||
/**
|
||||
* Sent by the client to request general information about the server.
|
||||
*
|
||||
* The server responds with a [ResponsePacket].
|
||||
*
|
||||
* @see [space.blokk.net.events.ServerListInfoRequestEvent]
|
||||
*/
|
||||
class RequestPacket : IncomingPacket() {
|
||||
companion object : IncomingPacketCompanion<RequestPacket>(0x00, RequestPacket::class) {
|
||||
override fun decode(msg: ByteBuf): RequestPacket = RequestPacket()
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package space.blokk.net.protocols.status
|
||||
|
||||
import space.blokk.net.protocols.Protocol
|
||||
|
||||
object StatusProtocol : Protocol(
|
||||
"STATUS",
|
||||
RequestPacket,
|
||||
ResponsePacket,
|
||||
PingPacket,
|
||||
PongPacket
|
||||
)
|
|
@ -1,12 +0,0 @@
|
|||
package space.blokk.net.protocols
|
||||
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
|
||||
object ProtocolsTest : Spek({
|
||||
describe("Protocols") {
|
||||
it("are valid") {
|
||||
Protocols.validate()
|
||||
}
|
||||
}
|
||||
})
|
25
blokk-packet-codecs/build.gradle.kts
Normal file
25
blokk-packet-codecs/build.gradle.kts
Normal file
|
@ -0,0 +1,25 @@
|
|||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
group = rootProject.group
|
||||
version = rootProject.version
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
val nettyVersion = properties["version.netty"].toString()
|
||||
|
||||
dependencies {
|
||||
api(project(":blokk-api"))
|
||||
|
||||
// Netty
|
||||
api("io.netty:netty-buffer:${nettyVersion}")
|
||||
}
|
||||
|
||||
tasks {
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package space.blokk.net.packets.handshaking
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.readString
|
||||
import space.blokk.net.MinecraftDataTypes.readVarInt
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object HandshakePacketCodec : IncomingPacketCodec<HandshakePacket>(0x00, HandshakePacket::class) {
|
||||
override fun decode(msg: ByteBuf): HandshakePacket = HandshakePacket(
|
||||
protocolVersion = msg.readVarInt(),
|
||||
serverAddress = msg.readString(),
|
||||
serverPort = msg.readUnsignedShort(),
|
||||
loginAttempt = msg.readVarInt() == 2
|
||||
)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.net.packets.handshaking
|
||||
|
||||
import space.blokk.net.packets.Protocol
|
||||
|
||||
object HandshakingProtocol : Protocol("HANDSHAKING", HandshakePacketCodec)
|
|
@ -0,0 +1,11 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object DisconnectPacketCodec : OutgoingPacketCodec<DisconnectPacket>(0x00, DisconnectPacket::class) {
|
||||
override fun DisconnectPacket.encode(dst: ByteBuf) {
|
||||
dst.writeString(reason.toJson())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object EncryptionRequestPacketCodec :
|
||||
OutgoingPacketCodec<EncryptionRequestPacket>(0x01, EncryptionRequestPacket::class) {
|
||||
override fun EncryptionRequestPacket.encode(dst: ByteBuf) {
|
||||
dst.writeString("") // Required for legacy reasons
|
||||
dst.writeVarInt(publicKey.size)
|
||||
dst.writeBytes(publicKey)
|
||||
dst.writeVarInt(verifyToken.size)
|
||||
dst.writeBytes(verifyToken)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.readVarInt
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object EncryptionResponsePacketCodec :
|
||||
IncomingPacketCodec<EncryptionResponsePacket>(0x01, EncryptionResponsePacket::class) {
|
||||
override fun decode(msg: ByteBuf): EncryptionResponsePacket {
|
||||
val sharedSecretLength = msg.readVarInt()
|
||||
val sharedSecret = ByteArray(sharedSecretLength)
|
||||
msg.readBytes(sharedSecret)
|
||||
|
||||
val verifyTokenLength = msg.readVarInt()
|
||||
val verifyToken = ByteArray(verifyTokenLength)
|
||||
msg.readBytes(verifyToken)
|
||||
|
||||
return EncryptionResponsePacket(sharedSecret, verifyToken)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object LoginPluginRequestPacketCodec :
|
||||
OutgoingPacketCodec<LoginPluginRequestPacket>(0x04, LoginPluginRequestPacket::class) {
|
||||
override fun LoginPluginRequestPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(messageID)
|
||||
dst.writeString(channel)
|
||||
dst.writeBytes(data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.readVarInt
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object LoginPluginResponsePacketCodec :
|
||||
IncomingPacketCodec<LoginPluginResponsePacket>(0x02, LoginPluginResponsePacket::class) {
|
||||
override fun decode(msg: ByteBuf): LoginPluginResponsePacket {
|
||||
val messageID = msg.readVarInt()
|
||||
val successful = msg.readBoolean()
|
||||
|
||||
// TODO: Test this
|
||||
val data =
|
||||
if (successful) ByteArray(msg.readableBytes()).also { msg.readBytes(it) }
|
||||
else null
|
||||
|
||||
return LoginPluginResponsePacket(messageID, data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.packets.Protocol
|
||||
|
||||
object LoginProtocol : Protocol(
|
||||
"LOGIN",
|
||||
DisconnectPacketCodec,
|
||||
EncryptionRequestPacketCodec,
|
||||
EncryptionResponsePacketCodec,
|
||||
LoginPluginRequestPacketCodec,
|
||||
LoginPluginResponsePacketCodec,
|
||||
LoginStartPacketCodec,
|
||||
LoginSuccessPacketCodec,
|
||||
SetCompressionPacketCodec
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.readString
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object LoginStartPacketCodec : IncomingPacketCodec<LoginStartPacket>(0x00, LoginStartPacket::class) {
|
||||
override fun decode(msg: ByteBuf): LoginStartPacket = LoginStartPacket(msg.readString())
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object LoginSuccessPacketCodec : OutgoingPacketCodec<LoginSuccessPacket>(0x02, LoginSuccessPacket::class) {
|
||||
override fun LoginSuccessPacket.encode(dst: ByteBuf) {
|
||||
dst.writeString(uuid.toString())
|
||||
dst.writeString(username)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package space.blokk.net.packets.login
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object SetCompressionPacketCodec : OutgoingPacketCodec<SetCompressionPacket>(0x03, SetCompressionPacket::class) {
|
||||
override fun SetCompressionPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(threshold)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.readString
|
||||
import space.blokk.net.MinecraftDataTypes.readVarInt
|
||||
import space.blokk.net.PacketDecodingException
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
import space.blokk.players.ChatMode
|
||||
import space.blokk.players.DisplayedSkinParts
|
||||
import space.blokk.players.MainHand
|
||||
import space.blokk.utils.checkBit
|
||||
|
||||
object ClientSettingsPacketCodec : IncomingPacketCodec<ClientSettingsPacket>(0x05, ClientSettingsPacket::class) {
|
||||
override fun decode(msg: ByteBuf): ClientSettingsPacket {
|
||||
val locale = msg.readString()
|
||||
val viewDistance = msg.readByte()
|
||||
|
||||
val chatMode = when (msg.readVarInt()) {
|
||||
0 -> ChatMode.ENABLED
|
||||
1 -> ChatMode.COMMANDS_ONLY
|
||||
2 -> ChatMode.HIDDEN
|
||||
else -> throw PacketDecodingException()
|
||||
}
|
||||
|
||||
val chatColorsEnabled = msg.readBoolean()
|
||||
|
||||
val skinFlags = msg.readByte()
|
||||
val displayedSkinParts = DisplayedSkinParts(
|
||||
cape = skinFlags.checkBit(0),
|
||||
jacket = skinFlags.checkBit(1),
|
||||
leftSleeve = skinFlags.checkBit(2),
|
||||
rightSleeve = skinFlags.checkBit(3),
|
||||
leftPantsLeg = skinFlags.checkBit(4),
|
||||
rightPantsLeg = skinFlags.checkBit(5),
|
||||
hat = skinFlags.checkBit(6)
|
||||
)
|
||||
|
||||
val mainHand = when (msg.readVarInt()) {
|
||||
0 -> MainHand.LEFT
|
||||
1 -> MainHand.RIGHT
|
||||
else -> throw PacketDecodingException()
|
||||
}
|
||||
|
||||
return ClientSettingsPacket(
|
||||
locale,
|
||||
viewDistance,
|
||||
chatMode,
|
||||
chatColorsEnabled,
|
||||
displayedSkinParts,
|
||||
mainHand
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.readString
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object IncomingPluginMessagePacketCodec :
|
||||
IncomingPacketCodec<IncomingPluginMessagePacket>(0x0B, IncomingPluginMessagePacket::class) {
|
||||
override fun decode(msg: ByteBuf): IncomingPluginMessagePacket {
|
||||
val channel = msg.readString()
|
||||
|
||||
// TODO: Test this
|
||||
val data = ByteArray(msg.readableBytes()).also { msg.readBytes(it) }
|
||||
|
||||
return IncomingPluginMessagePacket(channel, data)
|
||||
}
|
||||
}
|
|
@ -1,45 +1,15 @@
|
|||
package space.blokk.net.protocols.play
|
||||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
import space.blokk.players.GameMode
|
||||
import space.blokk.worlds.WorldDimension
|
||||
import space.blokk.worlds.WorldType
|
||||
|
||||
/**
|
||||
* Sent by the server after the client logged in.
|
||||
*
|
||||
* @param entityID ID of the player entity.
|
||||
* @param gameMode Game mode of the player.
|
||||
* @param hardcore Whether the player is in hardcore mode.
|
||||
* @param worldDimension Dimension of the world the player joins.
|
||||
* @param worldSeedHash First 8 bytes of the SHA-256 hash of the world's seed.
|
||||
* @param worldType Type of the world the player joins.
|
||||
* @param viewDistance Maximum view distance.
|
||||
* @param reducedDebugInfo Whether the debug screen shows only reduced info.
|
||||
* @param respawnScreenEnabled Whether the respawn screen is shown when the player dies.
|
||||
*/
|
||||
data class JoinGamePacket(
|
||||
val entityID: Int,
|
||||
val gameMode: GameMode,
|
||||
val hardcore: Boolean,
|
||||
val worldDimension: WorldDimension,
|
||||
val worldSeedHash: Long,
|
||||
val worldType: WorldType,
|
||||
val viewDistance: Byte,
|
||||
val reducedDebugInfo: Boolean,
|
||||
val respawnScreenEnabled: Boolean
|
||||
) : OutgoingPacket() {
|
||||
companion object : OutgoingPacketCompanion<JoinGamePacket>(0x26, JoinGamePacket::class)
|
||||
|
||||
init {
|
||||
if (viewDistance < 2 || viewDistance > 32) throw IllegalArgumentException("viewDistance must be between 2 and 32.")
|
||||
}
|
||||
|
||||
override fun encode(dst: ByteBuf) {
|
||||
object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x26, JoinGamePacket::class) {
|
||||
override fun JoinGamePacket.encode(dst: ByteBuf) {
|
||||
dst.writeInt(entityID)
|
||||
var gameMode = when (gameMode) {
|
||||
GameMode.SURVIVAL -> 0
|
|
@ -0,0 +1,13 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object OutgoingPluginMessagePacketCodec :
|
||||
OutgoingPacketCodec<OutgoingPluginMessagePacket>(0x19, OutgoingPluginMessagePacket::class) {
|
||||
override fun OutgoingPluginMessagePacket.encode(dst: ByteBuf) {
|
||||
dst.writeString(channel)
|
||||
dst.writeBytes(data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import space.blokk.net.packets.Protocol
|
||||
|
||||
object PlayProtocol : Protocol(
|
||||
"PLAY",
|
||||
ClientSettingsPacketCodec,
|
||||
IncomingPluginMessagePacketCodec,
|
||||
JoinGamePacketCodec,
|
||||
OutgoingPluginMessagePacketCodec,
|
||||
PlayerAbilitiesPacketCodec,
|
||||
ServerDifficultyPacketCodec
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object PlayerAbilitiesPacketCodec : OutgoingPacketCodec<PlayerAbilitiesPacket>(0x32, PlayerAbilitiesPacket::class) {
|
||||
override fun PlayerAbilitiesPacket.encode(dst: ByteBuf) {
|
||||
var flags = 0
|
||||
if (invulnerable) flags = flags and 0x01
|
||||
if (flying) flags = flags and 0x02
|
||||
if (flyingAllowed) flags = flags and 0x04
|
||||
if (instantlyBreakBlocks) flags = flags and 0x08
|
||||
|
||||
dst.writeByte(flags)
|
||||
dst.writeFloat(flyingSpeed)
|
||||
dst.writeFloat(fieldOfView)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
import space.blokk.worlds.WorldDifficulty
|
||||
|
||||
object ServerDifficultyPacketCodec : OutgoingPacketCodec<ServerDifficultyPacket>(0x0E, ServerDifficultyPacket::class) {
|
||||
override fun ServerDifficultyPacket.encode(dst: ByteBuf) {
|
||||
dst.writeByte(
|
||||
when (difficulty) {
|
||||
WorldDifficulty.PEACEFUL -> 0
|
||||
WorldDifficulty.EASY -> 1
|
||||
WorldDifficulty.NORMAL -> 2
|
||||
WorldDifficulty.HARD -> 3
|
||||
}
|
||||
)
|
||||
|
||||
dst.writeBoolean(locked)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object PingPacketCodec : IncomingPacketCodec<PingPacket>(0x01, PingPacket::class) {
|
||||
override fun decode(msg: ByteBuf): PingPacket = PingPacket(msg.readLong())
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
|
||||
object PongPacketCodec : OutgoingPacketCodec<PongPacket>(0x01, PongPacket::class) {
|
||||
override fun PongPacket.encode(dst: ByteBuf) {
|
||||
dst.writeLong(payload)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.IncomingPacketCodec
|
||||
|
||||
object RequestPacketCodec : IncomingPacketCodec<RequestPacket>(0x00, RequestPacket::class) {
|
||||
override fun decode(msg: ByteBuf): RequestPacket = RequestPacket()
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.Blokk
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
import space.blokk.utils.toJson
|
||||
|
||||
object ResponsePacketCodec : OutgoingPacketCodec<ResponsePacket>(0x00, ResponsePacket::class) {
|
||||
override fun ResponsePacket.encode(dst: ByteBuf) {
|
||||
dst.writeString(
|
||||
Blokk.json.toJson(mapOf(
|
||||
"version" to mapOf(
|
||||
"name" to versionName,
|
||||
"protocol" to protocolVersion
|
||||
),
|
||||
"players" to players,
|
||||
"description" to description,
|
||||
"favicon" to favicon?.let { "data:image/png;base64,$it" }
|
||||
)))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package space.blokk.net.packets.status
|
||||
|
||||
import space.blokk.net.packets.Protocol
|
||||
|
||||
object StatusProtocol : Protocol(
|
||||
"STATUS",
|
||||
PingPacketCodec,
|
||||
RequestPacketCodec,
|
||||
PongPacketCodec,
|
||||
ResponsePacketCodec
|
||||
)
|
|
@ -1,6 +1,5 @@
|
|||
plugins {
|
||||
kotlin("jvm")
|
||||
kotlin("kapt")
|
||||
id("com.github.johnrengelman.shadow") version "6.0.0"
|
||||
}
|
||||
|
||||
|
@ -24,6 +23,7 @@ dependencies {
|
|||
|
||||
// Blokk
|
||||
implementation(project(":blokk-api"))
|
||||
implementation(project(":blokk-packet-codecs"))
|
||||
|
||||
// Logging
|
||||
implementation("org.slf4j:slf4j-api:${slf4jVersion}")
|
||||
|
@ -42,7 +42,6 @@ dependencies {
|
|||
implementation("com.squareup.okhttp3:okhttp:4.8.1")
|
||||
implementation("com.sksamuel.hoplite:hoplite-core:1.3.3")
|
||||
implementation("com.sksamuel.hoplite:hoplite-yaml:1.3.3")
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:${moshiVersion}")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
|
|
@ -5,21 +5,14 @@ import com.sksamuel.hoplite.ConfigLoader
|
|||
import com.sksamuel.hoplite.ConfigSource
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import space.blokk.chat.ChatComponent
|
||||
import space.blokk.chat.TextComponent
|
||||
import space.blokk.config.BlokkConfig
|
||||
import space.blokk.events.EventBus
|
||||
import space.blokk.events.EventHandler
|
||||
import space.blokk.events.Listener
|
||||
import space.blokk.logging.Logger
|
||||
import space.blokk.net.BlokkSocketServer
|
||||
import space.blokk.net.events.ServerListInfoRequestEvent
|
||||
import space.blokk.server.Server
|
||||
import space.blokk.server.events.ServerEvent
|
||||
import space.blokk.utils.EncryptionUtils
|
||||
import space.blokk.utils.Ticker
|
||||
import space.blokk.utils.delayTicks
|
||||
import java.io.File
|
||||
import java.security.KeyPair
|
||||
import kotlin.system.exitProcess
|
||||
|
@ -105,22 +98,7 @@ class BlokkServer internal constructor() : Server {
|
|||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val server = BlokkServer()
|
||||
Blokk.sessions.registerListener(object : Listener {
|
||||
@EventHandler
|
||||
fun onServerListInfoRequest(event: ServerListInfoRequestEvent) {
|
||||
event.response = event.response.copy(
|
||||
description = TextComponent(
|
||||
event.session.address.hostAddress,
|
||||
bold = true,
|
||||
underlined = true,
|
||||
color = ChatComponent.Color.RED
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
server.start()
|
||||
BlokkServer().start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class BlokkChannelInitializer(private val blokkSocketServer: BlokkSocketServer)
|
|||
channel.pipeline()
|
||||
.addLast("idle", IdleStateHandler(20, 15, 0))
|
||||
.addLast("framing", FramingCodec())
|
||||
.addLast("packets", PacketCodec(session))
|
||||
.addLast("packets", PacketMessageCodec(session))
|
||||
.addLast("handler", PacketMessageHandler(session))
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@ import space.blokk.logging.Logger
|
|||
import space.blokk.net.events.PacketReceivedEvent
|
||||
import space.blokk.net.events.PacketSendEvent
|
||||
import space.blokk.net.events.SessionEvent
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.protocols.Protocol
|
||||
import space.blokk.net.protocols.handshaking.HandshakingProtocol
|
||||
import space.blokk.net.protocols.login.DisconnectPacket
|
||||
import space.blokk.net.protocols.login.LoginProtocol
|
||||
import space.blokk.net.protocols.play.OutgoingPluginMessagePacket
|
||||
import space.blokk.net.protocols.play.PlayProtocol
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
import space.blokk.net.packets.Protocol
|
||||
import space.blokk.net.packets.handshaking.HandshakingProtocol
|
||||
import space.blokk.net.packets.login.DisconnectPacket
|
||||
import space.blokk.net.packets.login.LoginProtocol
|
||||
import space.blokk.net.packets.play.OutgoingPluginMessagePacket
|
||||
import space.blokk.net.packets.play.PlayProtocol
|
||||
import space.blokk.server.events.SessionInitializedEvent
|
||||
import space.blokk.utils.awaitSuspending
|
||||
import java.net.InetAddress
|
||||
|
@ -107,7 +107,7 @@ class BlokkSession(private val channel: Channel) : Session {
|
|||
logger trace { "Sending packet: $packet" }
|
||||
eventBus.emit(PacketSendEvent(this@BlokkSession, packet)).ifNotCancelled {
|
||||
try {
|
||||
channel.writeAndFlush(PacketMessage(this@BlokkSession, it.packet)).awaitSuspending()
|
||||
channel.writeAndFlush(OutgoingPacketMessage(this@BlokkSession, it.packet)).awaitSuspending()
|
||||
} catch (t: Throwable) {
|
||||
logger.error("Packet send failed:", t)
|
||||
}
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
package space.blokk.net
|
||||
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||
import space.blokk.net.protocols.handshaking.HandshakingProtocol
|
||||
import space.blokk.net.protocols.handshaking.HandshakingProtocolHandler
|
||||
import space.blokk.net.protocols.login.LoginProtocol
|
||||
import space.blokk.net.protocols.login.LoginProtocolHandler
|
||||
import space.blokk.net.protocols.play.PlayProtocol
|
||||
import space.blokk.net.protocols.play.PlayProtocolHandler
|
||||
import space.blokk.net.protocols.status.StatusProtocol
|
||||
import space.blokk.net.protocols.status.StatusProtocolHandler
|
||||
|
||||
open class ProtocolPacketReceivedEventHandler(handlers: Map<out IncomingPacketCompanion<*>, PacketReceivedEventHandler<out IncomingPacket>>) {
|
||||
private val handlers = handlers.mapKeys { it.key.packetType }
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
import space.blokk.net.packets.handshaking.HandshakingProtocol
|
||||
import space.blokk.net.packets.handshaking.HandshakingProtocolHandler
|
||||
import space.blokk.net.packets.login.LoginProtocol
|
||||
import space.blokk.net.packets.login.LoginProtocolHandler
|
||||
import space.blokk.net.packets.play.PlayProtocol
|
||||
import space.blokk.net.packets.play.PlayProtocolHandler
|
||||
import space.blokk.net.packets.status.StatusProtocol
|
||||
import space.blokk.net.packets.status.StatusProtocolHandler
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class ProtocolPacketReceivedEventHandler(private val handlers: Map<out KClass<out IncomingPacket>, PacketReceivedEventHandler<out IncomingPacket>>) {
|
||||
suspend fun handle(session: BlokkSession, packet: IncomingPacket) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val handler = handlers[packet::class] as PacketReceivedEventHandler<IncomingPacket>?
|
||||
val handler: PacketReceivedEventHandler<IncomingPacket>? =
|
||||
handlers[packet::class] as PacketReceivedEventHandler<IncomingPacket>?
|
||||
handler?.handle(session, packet)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,31 +3,32 @@ package space.blokk.net
|
|||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.handler.codec.ByteToMessageCodec
|
||||
import space.blokk.net.MinecraftDataTypes.readVarInt
|
||||
import space.blokk.net.MinecraftDataTypes.varIntReadable
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
|
||||
class FramingCodec : ByteToMessageCodec<ByteBuf>() {
|
||||
private var currentLength: Int? = null
|
||||
|
||||
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) {
|
||||
with(MinecraftDataTypes) { out.writeVarInt(msg.readableBytes()) }
|
||||
out.writeVarInt(msg.readableBytes())
|
||||
msg.readerIndex(0)
|
||||
out.writeBytes(msg)
|
||||
}
|
||||
|
||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||
with(MinecraftDataTypes) {
|
||||
var length = currentLength
|
||||
var length = currentLength
|
||||
|
||||
if (length == null) {
|
||||
if (msg.varIntReadable()) {
|
||||
length = msg.readVarInt()
|
||||
currentLength = length
|
||||
} else return
|
||||
}
|
||||
if (length == null) {
|
||||
if (msg.varIntReadable()) {
|
||||
length = msg.readVarInt()
|
||||
currentLength = length
|
||||
} else return
|
||||
}
|
||||
|
||||
if (msg.readableBytes() >= length) {
|
||||
out.add(msg.readBytes(length))
|
||||
currentLength = null
|
||||
}
|
||||
if (msg.readableBytes() >= length) {
|
||||
out.add(msg.readBytes(length))
|
||||
currentLength = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ import io.netty.buffer.Unpooled
|
|||
import space.blokk.BlokkServer
|
||||
import space.blokk.chat.TextComponent
|
||||
import space.blokk.net.MinecraftDataTypes.writeString
|
||||
import space.blokk.net.protocols.login.EncryptionRequestPacket
|
||||
import space.blokk.net.protocols.login.EncryptionResponsePacket
|
||||
import space.blokk.net.protocols.login.LoginStartPacket
|
||||
import space.blokk.net.protocols.login.LoginSuccessPacket
|
||||
import space.blokk.net.protocols.play.JoinGamePacket
|
||||
import space.blokk.net.protocols.play.PlayProtocol
|
||||
import space.blokk.net.protocols.play.PlayerAbilitiesPacket
|
||||
import space.blokk.net.protocols.play.ServerDifficultyPacket
|
||||
import space.blokk.net.packets.login.EncryptionRequestPacket
|
||||
import space.blokk.net.packets.login.EncryptionResponsePacket
|
||||
import space.blokk.net.packets.login.LoginStartPacket
|
||||
import space.blokk.net.packets.login.LoginSuccessPacket
|
||||
import space.blokk.net.packets.play.JoinGamePacket
|
||||
import space.blokk.net.packets.play.PlayProtocol
|
||||
import space.blokk.net.packets.play.PlayerAbilitiesPacket
|
||||
import space.blokk.net.packets.play.ServerDifficultyPacket
|
||||
import space.blokk.players.GameMode
|
||||
import space.blokk.utils.AuthenticationHelper
|
||||
import space.blokk.utils.EncryptionUtils
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package space.blokk.net
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.packets.IncomingPacket
|
||||
import space.blokk.net.packets.OutgoingPacket
|
||||
import space.blokk.net.packets.OutgoingPacketCodec
|
||||
import space.blokk.net.packets.Packet
|
||||
|
||||
abstract class PacketMessage<T : Packet>(val session: BlokkSession, val packet: T)
|
||||
|
||||
class OutgoingPacketMessage<T : OutgoingPacket>(session: BlokkSession, packet: T) : PacketMessage<T>(session, packet) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val packetCodec = session.currentProtocol.getCodecByType(packet::class) as OutgoingPacketCodec<T>
|
||||
fun encodePacket(dst: ByteBuf) = with(packetCodec) { packet.encode(dst) }
|
||||
}
|
||||
|
||||
class IncomingPacketMessage<T : IncomingPacket>(session: BlokkSession, packet: T) : PacketMessage<T>(session, packet)
|
|
@ -1,15 +0,0 @@
|
|||
package space.blokk.net
|
||||
|
||||
import space.blokk.net.protocols.Packet
|
||||
import space.blokk.net.protocols.PacketCompanion
|
||||
|
||||
data class PacketMessage<T : Packet>(val session: BlokkSession, val packet: T) {
|
||||
val packetCompanion by lazy {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
session.currentProtocol.packetCompanionsByPacketType[packet::class] as PacketCompanion<T>?
|
||||
?: throw Exception(
|
||||
"No packet companion found for this packet type. " +
|
||||
"This can happen if the packet is not part of the current protocol."
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,10 +3,11 @@ package space.blokk.net
|
|||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.handler.codec.MessageToMessageCodec
|
||||
import space.blokk.net.protocols.OutgoingPacket
|
||||
import space.blokk.net.MinecraftDataTypes.readVarInt
|
||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||
import java.io.IOException
|
||||
|
||||
class PacketCodec(private val session: BlokkSession) : MessageToMessageCodec<ByteBuf, PacketMessage<*>>() {
|
||||
class PacketMessageCodec(private val session: BlokkSession) : MessageToMessageCodec<ByteBuf, PacketMessage<*>>() {
|
||||
override fun channelActive(ctx: ChannelHandlerContext) {
|
||||
session.onConnect()
|
||||
}
|
||||
|
@ -16,21 +17,22 @@ class PacketCodec(private val session: BlokkSession) : MessageToMessageCodec<Byt
|
|||
}
|
||||
|
||||
override fun encode(ctx: ChannelHandlerContext, msg: PacketMessage<*>, out: MutableList<Any>) {
|
||||
if (msg.packet !is OutgoingPacket) throw Error("Only clientbound packets are allowed. This should never happen.")
|
||||
val buffer = ctx.alloc().buffer()
|
||||
with(MinecraftDataTypes) { buffer.writeVarInt(msg.packetCompanion.id) }
|
||||
msg.packet.encode(buffer)
|
||||
out.add(buffer)
|
||||
if (msg !is OutgoingPacketMessage<*>) throw UnsupportedOperationException("Only outgoing packets can get encoded")
|
||||
|
||||
val dst = ctx.alloc().buffer()
|
||||
dst.writeVarInt(msg.packetCodec.id)
|
||||
msg.encodePacket(dst)
|
||||
out.add(dst)
|
||||
}
|
||||
|
||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||
val packetID = with(MinecraftDataTypes) { msg.readVarInt() }
|
||||
val packetID = msg.readVarInt()
|
||||
val data = msg.readBytes(msg.readableBytes())
|
||||
|
||||
val packetCompanion = session.currentProtocol.incomingPacketsByID[packetID]
|
||||
val codec = session.currentProtocol.incomingPacketCodecsByID[packetID]
|
||||
?: throw IOException("Client sent an unknown packet (ID: $packetID)")
|
||||
|
||||
out.add(PacketMessage(session, packetCompanion.decode(data)))
|
||||
out.add(IncomingPacketMessage(session, codec.decode(data)))
|
||||
}
|
||||
|
||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
|
@ -4,11 +4,10 @@ import io.netty.channel.ChannelHandlerContext
|
|||
import io.netty.channel.SimpleChannelInboundHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import space.blokk.net.events.PacketReceivedEvent
|
||||
import space.blokk.net.protocols.IncomingPacket
|
||||
|
||||
class PacketMessageHandler(private val session: BlokkSession) : SimpleChannelInboundHandler<PacketMessage<*>>() {
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: PacketMessage<*>) {
|
||||
if (msg.packet !is IncomingPacket) throw Error("Only serverbound packets are allowed. This should never happen.")
|
||||
class PacketMessageHandler(private val session: BlokkSession) :
|
||||
SimpleChannelInboundHandler<IncomingPacketMessage<*>>() {
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: IncomingPacketMessage<*>) {
|
||||
session.logger.trace { "Packet received: ${msg.packet}" }
|
||||
session.scope.launch { session.eventBus.emit(PacketReceivedEvent(session, msg.packet)) }
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package space.blokk.net.protocols.handshaking
|
||||
package space.blokk.net.packets.handshaking
|
||||
|
||||
import space.blokk.net.PacketReceivedEventHandler
|
||||
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
||||
import space.blokk.net.protocols.login.LoginProtocol
|
||||
import space.blokk.net.protocols.status.StatusProtocol
|
||||
import space.blokk.net.packets.login.LoginProtocol
|
||||
import space.blokk.net.packets.status.StatusProtocol
|
||||
|
||||
object HandshakingProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
||||
HandshakePacket to PacketReceivedEventHandler.of<HandshakePacket> { session, packet ->
|
||||
HandshakePacket::class to PacketReceivedEventHandler.of<HandshakePacket> { session, packet ->
|
||||
session.currentProtocol = if (packet.loginAttempt) LoginProtocol else StatusProtocol
|
||||
}
|
||||
))
|
|
@ -1,4 +1,4 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.BlokkSession
|
||||
import space.blokk.net.PacketReceivedEventHandler
|
|
@ -1,9 +1,11 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
||||
|
||||
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
||||
object LoginProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
||||
LoginStartPacket to LoginStartPacketHandler,
|
||||
EncryptionResponsePacket to EncryptionResponsePacketHandler
|
||||
))
|
||||
object LoginProtocolHandler : ProtocolPacketReceivedEventHandler(
|
||||
mapOf(
|
||||
LoginStartPacket::class to LoginStartPacketHandler,
|
||||
EncryptionResponsePacket::class to EncryptionResponsePacketHandler
|
||||
)
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package space.blokk.net.protocols.login
|
||||
package space.blokk.net.packets.login
|
||||
|
||||
import space.blokk.net.BlokkSession
|
||||
import space.blokk.net.PacketReceivedEventHandler
|
|
@ -1,4 +1,4 @@
|
|||
package space.blokk.net.protocols.play
|
||||
package space.blokk.net.packets.play
|
||||
|
||||
import space.blokk.net.BlokkSession
|
||||
import space.blokk.net.PacketReceivedEventHandler
|
|
@ -1,4 +1,4 @@
|
|||
package space.blokk.net.protocols.play
|
||||
package space.blokk.net.packets.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.Unpooled
|
|
@ -1,11 +1,11 @@
|
|||
package space.blokk.net.protocols.play
|
||||
package space.blokk.net.packets.play
|
||||
|
||||
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
||||
|
||||
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
||||
object PlayProtocolHandler : ProtocolPacketReceivedEventHandler(
|
||||
mapOf(
|
||||
ClientSettingsPacket to ClientSettingsPacketHandler,
|
||||
IncomingPluginMessagePacket to IncomingPluginMessagePacketHandler
|
||||
ClientSettingsPacket::class to ClientSettingsPacketHandler,
|
||||
IncomingPluginMessagePacket::class to IncomingPluginMessagePacketHandler
|
||||
)
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package space.blokk.net.protocols.status
|
||||
package space.blokk.net.packets.status
|
||||
|
||||
import space.blokk.chat.FormattingCode
|
||||
import space.blokk.chat.TextComponent
|
||||
|
@ -10,7 +10,7 @@ import java.util.*
|
|||
|
||||
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
||||
object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
||||
RequestPacket to PacketReceivedEventHandler.of<RequestPacket> { session, _ ->
|
||||
RequestPacket::class to PacketReceivedEventHandler.of<RequestPacket> { session, _ ->
|
||||
session.eventBus.emit(
|
||||
ServerListInfoRequestEvent(
|
||||
session,
|
||||
|
@ -33,7 +33,7 @@ object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
|||
)
|
||||
).ifNotCancelled { session.send(it.response) }
|
||||
},
|
||||
PingPacket to PacketReceivedEventHandler.of<PingPacket> { session, packet ->
|
||||
PingPacket::class to PacketReceivedEventHandler.of<PingPacket> { session, packet ->
|
||||
session.send(PongPacket(packet.payload))
|
||||
}
|
||||
))
|
|
@ -4,4 +4,5 @@
|
|||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
<logger name="io.netty" level="WARN"/>
|
||||
<logger name="ch.qos.logback" level="OFF"/>
|
||||
</configuration>
|
||||
|
|
|
@ -9,3 +9,4 @@ rootProject.name = "blokk"
|
|||
|
||||
include(":blokk-api")
|
||||
include(":blokk-server")
|
||||
include(":blokk-packet-codecs")
|
||||
|
|
Reference in a new issue