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/
|
/.gradle/
|
||||||
/build/
|
build/
|
||||||
/blokk-api/build/
|
!src/**/build
|
||||||
/blokk-server/build/
|
|
||||||
|
|
||||||
/data/
|
/data/
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Blokk
|
# Blokk
|
||||||
## To Do
|
## To Do
|
||||||
- Move packets to their own project
|
- 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,
|
* Legacy formatting codes. You should use [ChatComponent][space.blokk.chat.ChatComponent] whenever it's possible,
|
||||||
* but sometimes these codes are required, for example in
|
* 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) {
|
enum class FormattingCode(private val char: Char) {
|
||||||
BLACK('0'),
|
BLACK('0'),
|
||||||
|
|
|
@ -4,8 +4,8 @@ import io.netty.buffer.ByteBuf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import space.blokk.events.EventTarget
|
import space.blokk.events.EventTarget
|
||||||
import space.blokk.net.events.SessionEvent
|
import space.blokk.net.events.SessionEvent
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
import space.blokk.net.packets.OutgoingPacket
|
||||||
import space.blokk.net.protocols.Protocol
|
import space.blokk.net.packets.Protocol
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
interface Session : EventTarget<SessionEvent> {
|
interface Session : EventTarget<SessionEvent> {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package space.blokk.net.events
|
||||||
|
|
||||||
import space.blokk.events.Cancellable
|
import space.blokk.events.Cancellable
|
||||||
import space.blokk.net.Session
|
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.**
|
* 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.events.Cancellable
|
||||||
import space.blokk.net.Session
|
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.**
|
* 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.events.Cancellable
|
||||||
import space.blokk.net.Session
|
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.
|
* 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.packets.OutgoingPacket
|
||||||
import space.blokk.net.MinecraftDataTypes.writeString
|
|
||||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by the server to request authentication and encryption from the client. The client must respond with
|
* 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 publicKey: ByteArray,
|
||||||
val verifyToken: ByteArray
|
val verifyToken: ByteArray
|
||||||
) : OutgoingPacket() {
|
) : 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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
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.packets.OutgoingPacket
|
||||||
import space.blokk.net.MinecraftDataTypes.writeString
|
|
||||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optionally sent by the server to transfer additional, non-standard information required for the login process.
|
* 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 channel: String,
|
||||||
val data: ByteArray
|
val data: ByteArray
|
||||||
) : OutgoingPacket() {
|
) : 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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
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.packets.IncomingPacket
|
||||||
import space.blokk.net.MinecraftDataTypes
|
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
|
||||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by the client in response to a [LoginPluginRequestPacket].
|
* Sent by the client in response to a [LoginPluginRequestPacket].
|
||||||
|
@ -14,22 +11,6 @@ data class LoginPluginResponsePacket(
|
||||||
val messageID: Int,
|
val messageID: Int,
|
||||||
val data: ByteArray?
|
val data: ByteArray?
|
||||||
) : IncomingPacket() {
|
) : 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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
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.packets.OutgoingPacket
|
||||||
import space.blokk.net.MinecraftDataTypes.writeString
|
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by the server to indicate that the login process succeeded.
|
* 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
|
* 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 uuid The UUID of the player.
|
||||||
* @param username The username of the player. Can only be 16 characters long.
|
* @param username The username of the player. Can only be 16 characters long.
|
||||||
*/
|
*/
|
||||||
data class LoginSuccessPacket(val uuid: UUID, val username: String) : OutgoingPacket() {
|
data class LoginSuccessPacket(val uuid: UUID, val username: String) : OutgoingPacket() {
|
||||||
companion object : OutgoingPacketCompanion<LoginSuccessPacket>(0x02, LoginSuccessPacket::class)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (username.length > 16) throw IllegalArgumentException("username can only be 16 characters long")
|
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.packets.IncomingPacket
|
||||||
import space.blokk.net.MinecraftDataTypes
|
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
|
||||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be used by client-side mods to communicate with a potentially modded server.
|
* 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.
|
* @param data Data which was sent.
|
||||||
*/
|
*/
|
||||||
data class IncomingPluginMessagePacket(val channel: String, val data: ByteArray) : IncomingPacket() {
|
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 {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
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.packets.OutgoingPacket
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the client some things about the player.
|
* Tells the client some things about the player.
|
||||||
|
@ -21,18 +19,4 @@ data class PlayerAbilitiesPacket(
|
||||||
val instantlyBreakBlocks: Boolean,
|
val instantlyBreakBlocks: Boolean,
|
||||||
val flyingSpeed: Float = 0.5f,
|
val flyingSpeed: Float = 0.5f,
|
||||||
val fieldOfView: Float = 0.1f
|
val fieldOfView: Float = 0.1f
|
||||||
) : OutgoingPacket() {
|
) : 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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 com.squareup.moshi.JsonClass
|
||||||
import io.netty.buffer.ByteBuf
|
|
||||||
import space.blokk.Blokk
|
|
||||||
import space.blokk.chat.TextComponent
|
import space.blokk.chat.TextComponent
|
||||||
import space.blokk.net.MinecraftDataTypes
|
import space.blokk.net.packets.OutgoingPacket
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
|
||||||
import space.blokk.utils.toJson
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sent by the server in response to a [RequestPacket].
|
* Sent by the server in response to a [RequestPacket].
|
||||||
|
@ -26,27 +21,11 @@ data class ResponsePacket(
|
||||||
val players: Players,
|
val players: Players,
|
||||||
val favicon: String? = null
|
val favicon: String? = null
|
||||||
) : OutgoingPacket() {
|
) : OutgoingPacket() {
|
||||||
companion object : OutgoingPacketCompanion<ResponsePacket>(0x00, ResponsePacket::class)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (description.getExtraTypes().find { it != TextComponent::class } != null)
|
if (description.getExtraTypes().find { it != TextComponent::class } != null)
|
||||||
throw Exception("description may only contain instances of TextComponent")
|
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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Players(val max: Int, val online: Int, val sample: List<SampleEntry>) {
|
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 io.netty.buffer.ByteBuf
|
||||||
import space.blokk.net.MinecraftDataTypes.writeString
|
import space.blokk.net.MinecraftDataTypes.writeString
|
||||||
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
import space.blokk.net.MinecraftDataTypes.writeVarInt
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
import space.blokk.net.packets.OutgoingPacketCodec
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
|
||||||
import space.blokk.players.GameMode
|
import space.blokk.players.GameMode
|
||||||
import space.blokk.worlds.WorldDimension
|
import space.blokk.worlds.WorldDimension
|
||||||
import space.blokk.worlds.WorldType
|
import space.blokk.worlds.WorldType
|
||||||
|
|
||||||
/**
|
object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x26, JoinGamePacket::class) {
|
||||||
* Sent by the server after the client logged in.
|
override fun JoinGamePacket.encode(dst: ByteBuf) {
|
||||||
*
|
|
||||||
* @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) {
|
|
||||||
dst.writeInt(entityID)
|
dst.writeInt(entityID)
|
||||||
var gameMode = when (gameMode) {
|
var gameMode = when (gameMode) {
|
||||||
GameMode.SURVIVAL -> 0
|
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 {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
kotlin("kapt")
|
|
||||||
id("com.github.johnrengelman.shadow") version "6.0.0"
|
id("com.github.johnrengelman.shadow") version "6.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +23,7 @@ dependencies {
|
||||||
|
|
||||||
// Blokk
|
// Blokk
|
||||||
implementation(project(":blokk-api"))
|
implementation(project(":blokk-api"))
|
||||||
|
implementation(project(":blokk-packet-codecs"))
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.slf4j:slf4j-api:${slf4jVersion}")
|
implementation("org.slf4j:slf4j-api:${slf4jVersion}")
|
||||||
|
@ -42,7 +42,6 @@ dependencies {
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.8.1")
|
implementation("com.squareup.okhttp3:okhttp:4.8.1")
|
||||||
implementation("com.sksamuel.hoplite:hoplite-core:1.3.3")
|
implementation("com.sksamuel.hoplite:hoplite-core:1.3.3")
|
||||||
implementation("com.sksamuel.hoplite:hoplite-yaml:1.3.3")
|
implementation("com.sksamuel.hoplite:hoplite-yaml:1.3.3")
|
||||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:${moshiVersion}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
|
|
@ -5,21 +5,14 @@ import com.sksamuel.hoplite.ConfigLoader
|
||||||
import com.sksamuel.hoplite.ConfigSource
|
import com.sksamuel.hoplite.ConfigSource
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
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.config.BlokkConfig
|
||||||
import space.blokk.events.EventBus
|
import space.blokk.events.EventBus
|
||||||
import space.blokk.events.EventHandler
|
|
||||||
import space.blokk.events.Listener
|
|
||||||
import space.blokk.logging.Logger
|
import space.blokk.logging.Logger
|
||||||
import space.blokk.net.BlokkSocketServer
|
import space.blokk.net.BlokkSocketServer
|
||||||
import space.blokk.net.events.ServerListInfoRequestEvent
|
|
||||||
import space.blokk.server.Server
|
import space.blokk.server.Server
|
||||||
import space.blokk.server.events.ServerEvent
|
import space.blokk.server.events.ServerEvent
|
||||||
import space.blokk.utils.EncryptionUtils
|
import space.blokk.utils.EncryptionUtils
|
||||||
import space.blokk.utils.Ticker
|
import space.blokk.utils.Ticker
|
||||||
import space.blokk.utils.delayTicks
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
@ -105,22 +98,7 @@ class BlokkServer internal constructor() : Server {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val server = BlokkServer()
|
BlokkServer().start()
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ class BlokkChannelInitializer(private val blokkSocketServer: BlokkSocketServer)
|
||||||
channel.pipeline()
|
channel.pipeline()
|
||||||
.addLast("idle", IdleStateHandler(20, 15, 0))
|
.addLast("idle", IdleStateHandler(20, 15, 0))
|
||||||
.addLast("framing", FramingCodec())
|
.addLast("framing", FramingCodec())
|
||||||
.addLast("packets", PacketCodec(session))
|
.addLast("packets", PacketMessageCodec(session))
|
||||||
.addLast("handler", PacketMessageHandler(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.PacketReceivedEvent
|
||||||
import space.blokk.net.events.PacketSendEvent
|
import space.blokk.net.events.PacketSendEvent
|
||||||
import space.blokk.net.events.SessionEvent
|
import space.blokk.net.events.SessionEvent
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
import space.blokk.net.packets.OutgoingPacket
|
||||||
import space.blokk.net.protocols.Protocol
|
import space.blokk.net.packets.Protocol
|
||||||
import space.blokk.net.protocols.handshaking.HandshakingProtocol
|
import space.blokk.net.packets.handshaking.HandshakingProtocol
|
||||||
import space.blokk.net.protocols.login.DisconnectPacket
|
import space.blokk.net.packets.login.DisconnectPacket
|
||||||
import space.blokk.net.protocols.login.LoginProtocol
|
import space.blokk.net.packets.login.LoginProtocol
|
||||||
import space.blokk.net.protocols.play.OutgoingPluginMessagePacket
|
import space.blokk.net.packets.play.OutgoingPluginMessagePacket
|
||||||
import space.blokk.net.protocols.play.PlayProtocol
|
import space.blokk.net.packets.play.PlayProtocol
|
||||||
import space.blokk.server.events.SessionInitializedEvent
|
import space.blokk.server.events.SessionInitializedEvent
|
||||||
import space.blokk.utils.awaitSuspending
|
import space.blokk.utils.awaitSuspending
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
@ -107,7 +107,7 @@ class BlokkSession(private val channel: Channel) : Session {
|
||||||
logger trace { "Sending packet: $packet" }
|
logger trace { "Sending packet: $packet" }
|
||||||
eventBus.emit(PacketSendEvent(this@BlokkSession, packet)).ifNotCancelled {
|
eventBus.emit(PacketSendEvent(this@BlokkSession, packet)).ifNotCancelled {
|
||||||
try {
|
try {
|
||||||
channel.writeAndFlush(PacketMessage(this@BlokkSession, it.packet)).awaitSuspending()
|
channel.writeAndFlush(OutgoingPacketMessage(this@BlokkSession, it.packet)).awaitSuspending()
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logger.error("Packet send failed:", t)
|
logger.error("Packet send failed:", t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
package space.blokk.net
|
package space.blokk.net
|
||||||
|
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
import space.blokk.net.packets.IncomingPacket
|
||||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
import space.blokk.net.packets.handshaking.HandshakingProtocol
|
||||||
import space.blokk.net.protocols.handshaking.HandshakingProtocol
|
import space.blokk.net.packets.handshaking.HandshakingProtocolHandler
|
||||||
import space.blokk.net.protocols.handshaking.HandshakingProtocolHandler
|
import space.blokk.net.packets.login.LoginProtocol
|
||||||
import space.blokk.net.protocols.login.LoginProtocol
|
import space.blokk.net.packets.login.LoginProtocolHandler
|
||||||
import space.blokk.net.protocols.login.LoginProtocolHandler
|
import space.blokk.net.packets.play.PlayProtocol
|
||||||
import space.blokk.net.protocols.play.PlayProtocol
|
import space.blokk.net.packets.play.PlayProtocolHandler
|
||||||
import space.blokk.net.protocols.play.PlayProtocolHandler
|
import space.blokk.net.packets.status.StatusProtocol
|
||||||
import space.blokk.net.protocols.status.StatusProtocol
|
import space.blokk.net.packets.status.StatusProtocolHandler
|
||||||
import space.blokk.net.protocols.status.StatusProtocolHandler
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
open class ProtocolPacketReceivedEventHandler(handlers: Map<out IncomingPacketCompanion<*>, PacketReceivedEventHandler<out IncomingPacket>>) {
|
|
||||||
private val handlers = handlers.mapKeys { it.key.packetType }
|
|
||||||
|
|
||||||
|
open class ProtocolPacketReceivedEventHandler(private val handlers: Map<out KClass<out IncomingPacket>, PacketReceivedEventHandler<out IncomingPacket>>) {
|
||||||
suspend fun handle(session: BlokkSession, packet: IncomingPacket) {
|
suspend fun handle(session: BlokkSession, packet: IncomingPacket) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@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)
|
handler?.handle(session, packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,31 +3,32 @@ package space.blokk.net
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.handler.codec.ByteToMessageCodec
|
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>() {
|
class FramingCodec : ByteToMessageCodec<ByteBuf>() {
|
||||||
private var currentLength: Int? = null
|
private var currentLength: Int? = null
|
||||||
|
|
||||||
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) {
|
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) {
|
||||||
with(MinecraftDataTypes) { out.writeVarInt(msg.readableBytes()) }
|
out.writeVarInt(msg.readableBytes())
|
||||||
msg.readerIndex(0)
|
msg.readerIndex(0)
|
||||||
out.writeBytes(msg)
|
out.writeBytes(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||||
with(MinecraftDataTypes) {
|
var length = currentLength
|
||||||
var length = currentLength
|
|
||||||
|
|
||||||
if (length == null) {
|
if (length == null) {
|
||||||
if (msg.varIntReadable()) {
|
if (msg.varIntReadable()) {
|
||||||
length = msg.readVarInt()
|
length = msg.readVarInt()
|
||||||
currentLength = length
|
currentLength = length
|
||||||
} else return
|
} else return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.readableBytes() >= length) {
|
if (msg.readableBytes() >= length) {
|
||||||
out.add(msg.readBytes(length))
|
out.add(msg.readBytes(length))
|
||||||
currentLength = null
|
currentLength = null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import io.netty.buffer.Unpooled
|
||||||
import space.blokk.BlokkServer
|
import space.blokk.BlokkServer
|
||||||
import space.blokk.chat.TextComponent
|
import space.blokk.chat.TextComponent
|
||||||
import space.blokk.net.MinecraftDataTypes.writeString
|
import space.blokk.net.MinecraftDataTypes.writeString
|
||||||
import space.blokk.net.protocols.login.EncryptionRequestPacket
|
import space.blokk.net.packets.login.EncryptionRequestPacket
|
||||||
import space.blokk.net.protocols.login.EncryptionResponsePacket
|
import space.blokk.net.packets.login.EncryptionResponsePacket
|
||||||
import space.blokk.net.protocols.login.LoginStartPacket
|
import space.blokk.net.packets.login.LoginStartPacket
|
||||||
import space.blokk.net.protocols.login.LoginSuccessPacket
|
import space.blokk.net.packets.login.LoginSuccessPacket
|
||||||
import space.blokk.net.protocols.play.JoinGamePacket
|
import space.blokk.net.packets.play.JoinGamePacket
|
||||||
import space.blokk.net.protocols.play.PlayProtocol
|
import space.blokk.net.packets.play.PlayProtocol
|
||||||
import space.blokk.net.protocols.play.PlayerAbilitiesPacket
|
import space.blokk.net.packets.play.PlayerAbilitiesPacket
|
||||||
import space.blokk.net.protocols.play.ServerDifficultyPacket
|
import space.blokk.net.packets.play.ServerDifficultyPacket
|
||||||
import space.blokk.players.GameMode
|
import space.blokk.players.GameMode
|
||||||
import space.blokk.utils.AuthenticationHelper
|
import space.blokk.utils.AuthenticationHelper
|
||||||
import space.blokk.utils.EncryptionUtils
|
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.buffer.ByteBuf
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.handler.codec.MessageToMessageCodec
|
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
|
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) {
|
override fun channelActive(ctx: ChannelHandlerContext) {
|
||||||
session.onConnect()
|
session.onConnect()
|
||||||
}
|
}
|
||||||
|
@ -16,21 +17,22 @@ class PacketCodec(private val session: BlokkSession) : MessageToMessageCodec<Byt
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(ctx: ChannelHandlerContext, msg: PacketMessage<*>, out: MutableList<Any>) {
|
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.")
|
if (msg !is OutgoingPacketMessage<*>) throw UnsupportedOperationException("Only outgoing packets can get encoded")
|
||||||
val buffer = ctx.alloc().buffer()
|
|
||||||
with(MinecraftDataTypes) { buffer.writeVarInt(msg.packetCompanion.id) }
|
val dst = ctx.alloc().buffer()
|
||||||
msg.packet.encode(buffer)
|
dst.writeVarInt(msg.packetCodec.id)
|
||||||
out.add(buffer)
|
msg.encodePacket(dst)
|
||||||
|
out.add(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
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 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)")
|
?: 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) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
|
@ -4,11 +4,10 @@ import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import space.blokk.net.events.PacketReceivedEvent
|
import space.blokk.net.events.PacketReceivedEvent
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
|
||||||
|
|
||||||
class PacketMessageHandler(private val session: BlokkSession) : SimpleChannelInboundHandler<PacketMessage<*>>() {
|
class PacketMessageHandler(private val session: BlokkSession) :
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: PacketMessage<*>) {
|
SimpleChannelInboundHandler<IncomingPacketMessage<*>>() {
|
||||||
if (msg.packet !is IncomingPacket) throw Error("Only serverbound packets are allowed. This should never happen.")
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: IncomingPacketMessage<*>) {
|
||||||
session.logger.trace { "Packet received: ${msg.packet}" }
|
session.logger.trace { "Packet received: ${msg.packet}" }
|
||||||
session.scope.launch { session.eventBus.emit(PacketReceivedEvent(session, 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.PacketReceivedEventHandler
|
||||||
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
||||||
import space.blokk.net.protocols.login.LoginProtocol
|
import space.blokk.net.packets.login.LoginProtocol
|
||||||
import space.blokk.net.protocols.status.StatusProtocol
|
import space.blokk.net.packets.status.StatusProtocol
|
||||||
|
|
||||||
object HandshakingProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
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
|
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.BlokkSession
|
||||||
import space.blokk.net.PacketReceivedEventHandler
|
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
|
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
||||||
|
|
||||||
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
||||||
object LoginProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
object LoginProtocolHandler : ProtocolPacketReceivedEventHandler(
|
||||||
LoginStartPacket to LoginStartPacketHandler,
|
mapOf(
|
||||||
EncryptionResponsePacket to EncryptionResponsePacketHandler
|
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.BlokkSession
|
||||||
import space.blokk.net.PacketReceivedEventHandler
|
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.BlokkSession
|
||||||
import space.blokk.net.PacketReceivedEventHandler
|
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.ByteBuf
|
||||||
import io.netty.buffer.Unpooled
|
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
|
import space.blokk.net.ProtocolPacketReceivedEventHandler
|
||||||
|
|
||||||
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
||||||
object PlayProtocolHandler : ProtocolPacketReceivedEventHandler(
|
object PlayProtocolHandler : ProtocolPacketReceivedEventHandler(
|
||||||
mapOf(
|
mapOf(
|
||||||
ClientSettingsPacket to ClientSettingsPacketHandler,
|
ClientSettingsPacket::class to ClientSettingsPacketHandler,
|
||||||
IncomingPluginMessagePacket to IncomingPluginMessagePacketHandler
|
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.FormattingCode
|
||||||
import space.blokk.chat.TextComponent
|
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
|
// NOTE: PacketReceivedEventHandler.of<T> MUST have T specified correctly, otherwise the code breaks at runtime
|
||||||
object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
||||||
RequestPacket to PacketReceivedEventHandler.of<RequestPacket> { session, _ ->
|
RequestPacket::class to PacketReceivedEventHandler.of<RequestPacket> { session, _ ->
|
||||||
session.eventBus.emit(
|
session.eventBus.emit(
|
||||||
ServerListInfoRequestEvent(
|
ServerListInfoRequestEvent(
|
||||||
session,
|
session,
|
||||||
|
@ -33,7 +33,7 @@ object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
||||||
)
|
)
|
||||||
).ifNotCancelled { session.send(it.response) }
|
).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))
|
session.send(PongPacket(packet.payload))
|
||||||
}
|
}
|
||||||
))
|
))
|
|
@ -4,4 +4,5 @@
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="io.netty" level="WARN"/>
|
<logger name="io.netty" level="WARN"/>
|
||||||
|
<logger name="ch.qos.logback" level="OFF"/>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -9,3 +9,4 @@ rootProject.name = "blokk"
|
||||||
|
|
||||||
include(":blokk-api")
|
include(":blokk-api")
|
||||||
include(":blokk-server")
|
include(":blokk-server")
|
||||||
|
include(":blokk-packet-codecs")
|
||||||
|
|
Reference in a new issue