diff --git a/gradle.properties b/gradle.properties index 5eb42959..82366e11 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ kotlin.code.style=official org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m kotlinVersion=1.9.0 -kommonsVersion=2.1.1 +kommonsVersion=2.1.5 ffiVersion=2.2.13 lwjglVersion=3.3.0 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 8f27d449..633247b8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -16,6 +16,8 @@ import java.io.BufferedInputStream import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File +import java.net.InetSocketAddress +import java.net.SocketAddress import java.util.* import java.util.concurrent.TimeUnit import java.util.zip.Inflater @@ -83,6 +85,7 @@ fun main() { client.connectToLocalServer(server.channels.createLocalChannel(), UUID.randomUUID()) //client2.connectToLocalServer(server.channels.createLocalChannel(), UUID.randomUUID()) + server.channels.createChannel(InetSocketAddress(21060)) } //ent.position += Vector2d(y = 14.0, x = -10.0) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientConnection.kt index c67f1f23..545d01f3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientConnection.kt @@ -8,22 +8,20 @@ import io.netty.channel.local.LocalAddress import io.netty.channel.local.LocalChannel import io.netty.channel.socket.nio.NioSocketChannel import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.ConnectionSide import ru.dbotthepony.kstarbound.network.ConnectionType import ru.dbotthepony.kstarbound.network.IClientPacket -import ru.dbotthepony.kstarbound.network.packets.HelloListener -import ru.dbotthepony.kstarbound.network.packets.HelloPacket +import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket import java.net.SocketAddress import java.util.* // client -> server class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: UUID) : Connection(ConnectionSide.CLIENT, type, uuid) { private fun sendHello() { - helloListener = HelloListener(this, channel!!).sendHello(localUUID) - } - - override fun onHelloReceived(helloPacket: HelloPacket) { + isLegacy = true + sendAndFlush(ProtocolRequestPacket(Starbound.LEGACY_PROTOCOL_VERSION)) } override fun inGame() { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 9ad78e85..72ad0dbb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -945,7 +945,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { val activeConnection = activeConnection - activeConnection?.send(TrackedPositionPacket(camera.pos)) + //activeConnection?.send(TrackedPositionPacket(camera.pos)) uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt index 669927e1..51805abc 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt @@ -15,7 +15,7 @@ import java.io.DataInputStream import java.io.DataOutputStream class ChunkCellsPacket(val pos: ChunkPos, val data: List) : IClientPacket { - constructor(stream: DataInputStream) : this(stream.readChunkPos(), stream.readCollection { MutableCell().read(stream).immutable() }) + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readChunkPos(), stream.readCollection { MutableCell().read(stream).immutable() }) constructor(chunk: Chunk<*, *>) : this(chunk.pos, ArrayList(CHUNK_SIZE * CHUNK_SIZE).also { for (x in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) { @@ -24,7 +24,7 @@ class ChunkCellsPacket(val pos: ChunkPos, val data: List) : IClie } }) - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeVec2i(pos) stream.writeCollection(data) { it.write(stream) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt index b65731d6..05ae5a50 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt @@ -9,9 +9,9 @@ import java.io.DataInputStream import java.io.DataOutputStream class ForgetChunkPacket(val pos: ChunkPos) : IClientPacket { - constructor(stream: DataInputStream) : this(stream.readChunkPos()) + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readChunkPos()) - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeVec2i(pos) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt index f19dfc56..f05d9d73 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt @@ -9,9 +9,9 @@ import java.io.DataOutputStream import java.util.UUID class ForgetEntityPacket(val uuid: UUID) : IClientPacket { - constructor(buff: DataInputStream) : this(buff.readUUID()) + constructor(buff: DataInputStream, isLegacy: Boolean) : this(buff.readUUID()) - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeUUID(uuid) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt index 0fa4b389..caa85691 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt @@ -12,10 +12,10 @@ import java.io.DataOutputStream import java.util.* data class JoinWorldPacket(val uuid: UUID, val seed: Long, val geometry: WorldGeometry) : IClientPacket { - constructor(buff: DataInputStream) : this(buff.readUUID(), buff.readLong(), WorldGeometry(buff)) + constructor(buff: DataInputStream, isLegacy: Boolean) : this(buff.readUUID(), buff.readLong(), WorldGeometry(buff)) constructor(world: World<*, *>) : this(UUID(0L, 0L), world.seed, world.geometry) - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeUUID(uuid) stream.writeLong(seed) geometry.write(stream) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt index 8660a504..840444dd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt @@ -5,7 +5,7 @@ import ru.dbotthepony.kstarbound.network.IClientPacket import java.io.DataOutputStream object LeaveWorldPacket : IClientPacket { - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt index ea3662ec..edd7c16a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt @@ -13,9 +13,9 @@ import java.io.DataOutputStream import java.util.UUID class SpawnWorldObjectPacket(val uuid: UUID, val data: JsonObject) : IClientPacket { - constructor(stream: DataInputStream) : this(stream.readUUID(), stream.readJsonObject()) + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readUUID(), stream.readJsonObject()) - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeUUID(uuid) stream.writeJsonObject(data) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Celestial.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Celestial.kt new file mode 100644 index 00000000..06e7e8e1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Celestial.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.kstarbound.defs + +import ru.dbotthepony.kommons.io.writeStruct2i +import ru.dbotthepony.kommons.vector.Vector2i +import ru.dbotthepony.kstarbound.io.readVec2i +import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import java.io.DataInputStream +import java.io.DataOutputStream + +@JsonFactory +data class CelestialBaseInformation( + val planetOrbitalLevels: Int = 1, + val satelliteOrbitalLevels: Int = 1, + val chunkSize: Int = 1, + val xyCoordRange: Vector2i = Vector2i.ZERO, + val zCoordRange: Vector2i = Vector2i.ZERO, +) { + constructor(stream: DataInputStream, isLegacy: Boolean) : this( + stream.readInt(), + stream.readInt(), + stream.readInt(), + stream.readVec2i(), + stream.readVec2i(), + ) + + fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeInt(planetOrbitalLevels) + stream.writeInt(satelliteOrbitalLevels) + stream.writeInt(chunkSize) + stream.writeStruct2i(xyCoordRange) + stream.writeStruct2i(zCoordRange) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/ShipUpgrades.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/ShipUpgrades.kt new file mode 100644 index 00000000..47bcfc51 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/player/ShipUpgrades.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.kstarbound.defs.player + +import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kommons.collect.immutableSet +import ru.dbotthepony.kommons.io.readBinaryString +import ru.dbotthepony.kommons.io.readCollection +import ru.dbotthepony.kommons.io.writeBinaryString +import ru.dbotthepony.kommons.io.writeCollection +import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import java.io.DataInputStream +import java.io.DataOutputStream + +@JsonFactory +data class ShipUpgrades( + val shipLevel: Int = 0, + val maxFuel: Int = 0, + val crewSize: Int = 0, + val fuelEfficiency: Double = 1.0, + val shipSpeed: Int = 0, + val capabilities: ImmutableSet = ImmutableSet.of() +) { + fun apply(upgrades: ShipUpgrades): ShipUpgrades { + return ShipUpgrades( + shipLevel = shipLevel.coerceAtLeast(upgrades.shipLevel), + maxFuel = maxFuel.coerceAtLeast(upgrades.maxFuel), + crewSize = crewSize.coerceAtLeast(upgrades.crewSize), + fuelEfficiency = fuelEfficiency.coerceAtLeast(upgrades.fuelEfficiency), + shipSpeed = shipSpeed.coerceAtLeast(upgrades.shipSpeed), + capabilities = immutableSet { capabilities.forEach(::accept); upgrades.capabilities.forEach(::accept) } + ) + } + + fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeInt(shipLevel) + stream.writeInt(maxFuel) + stream.writeInt(crewSize) + + if (isLegacy) + stream.writeFloat(fuelEfficiency.toFloat()) + else + stream.writeDouble(fuelEfficiency) + + stream.writeInt(shipSpeed) + stream.writeCollection(capabilities) { writeBinaryString(it) } + } + + companion object { + fun read(stream: DataInputStream, isLegacy: Boolean): ShipUpgrades { + return ShipUpgrades( + stream.readInt(), + stream.readInt(), + stream.readInt(), + if (isLegacy) stream.readFloat().toDouble() else stream.readDouble(), + stream.readInt(), + ImmutableSet.copyOf(stream.readCollection { readBinaryString() }) + ) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/ByteKey.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/ByteKey.kt new file mode 100644 index 00000000..be217ac7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/ByteKey.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.kstarbound.io + +import ru.dbotthepony.kommons.io.readVarInt +import ru.dbotthepony.kommons.io.writeVarInt +import java.io.InputStream +import java.io.OutputStream + +class ByteKey(private vararg val bytes: Byte) { + override fun equals(other: Any?): Boolean { + return this === other || other is ByteKey && other.bytes.contentEquals(bytes) + } + + fun write(stream: OutputStream) { + stream.writeVarInt(bytes.size) + stream.write(bytes) + } + + override fun hashCode(): Int { + return bytes.contentHashCode() + } + + override fun toString(): String { + return "ByteKey[${bytes.joinToString(", ")}]" + } +} + +fun InputStream.readByteKey(): ByteKey { + return ByteKey(*ByteArray(readVarInt()).also { read(it) }) +} + +fun OutputStream.writeByteKey(key: ByteKey) { + key.write(this) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/API.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/API.kt index c2d4cd33..9a55ed68 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/API.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/API.kt @@ -76,11 +76,11 @@ enum class ConnectionType { } fun interface IPacketReader { - fun read(stream: DataInputStream): T + fun read(stream: DataInputStream, isLegacy: Boolean): T } interface IPacket { - fun write(stream: DataOutputStream) + fun write(stream: DataOutputStream, isLegacy: Boolean) } interface IServerPacket : IPacket { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt index 35d279b8..df49c653 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt @@ -7,111 +7,108 @@ import io.netty.channel.ChannelInboundHandlerAdapter import io.netty.channel.ChannelOption import io.netty.channel.nio.NioEventLoopGroup import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket -import ru.dbotthepony.kstarbound.network.packets.HelloListener -import ru.dbotthepony.kstarbound.network.packets.HelloPacket import ru.dbotthepony.kstarbound.player.Avatar import ru.dbotthepony.kstarbound.world.entities.PlayerEntity +import java.io.Closeable import java.util.* +import kotlin.properties.Delegates -abstract class Connection(val side: ConnectionSide, val type: ConnectionType, val localUUID: UUID) : ChannelInboundHandlerAdapter(), IConnectionDetails { +abstract class Connection(val side: ConnectionSide, val type: ConnectionType, val localUUID: UUID) : ChannelInboundHandlerAdapter(), Closeable { abstract override fun channelRead(ctx: ChannelHandlerContext, msg: Any) var avatar: Avatar? = null var character: PlayerEntity? = null - protected var channel: Channel? = null - protected var otherSide: HelloPacket? = null - protected var helloListener: HelloListener? = null + var channel: Channel by Delegates.notNull() + protected set - override val protocolVersion: Int - get() = otherSide?.protocolVersion ?: 0 - override val engineVersion: String - get() = otherSide?.engineVersion ?: "0.0.0" - override val username: String - get() = otherSide?.username ?: "" - override val password: String - get() = otherSide?.password ?: "" - override val uuid: UUID - get() = otherSide?.uuid ?: EMPTY_UUID + var isLegacy: Boolean = true + protected set + + private val handshakeValidator = PacketRegistry.HANDSHAKE.Validator(side) + private val handshakeSerializer = PacketRegistry.HANDSHAKE.Serializer(side) + + private val nativeValidator = PacketRegistry.NATIVE.Validator(side) + private val nativeSerializer = PacketRegistry.NATIVE.Serializer(side) + + private val legacyValidator = PacketRegistry.LEGACY.Validator(side) + private val legacySerializer = PacketRegistry.LEGACY.Serializer(side) + + fun setupLegacy() { + LOGGER.info("Handshake successful from ${channel.remoteAddress()}, channel is using legacy protocol") + + if (type == ConnectionType.MEMORY) { + channel.pipeline().remove(handshakeValidator) + channel.pipeline().addFirst(legacyValidator) + } else { + channel.pipeline().remove(handshakeSerializer) + channel.pipeline().addFirst(legacySerializer) + } + + isLegacy = true + } + + fun setupNative() { + LOGGER.info("Handshake successful from ${channel.remoteAddress()}, channel is using native protocol") + + if (type == ConnectionType.MEMORY) { + channel.pipeline().remove(handshakeValidator) + channel.pipeline().addFirst(nativeValidator) + } else { + channel.pipeline().remove(handshakeSerializer) + channel.pipeline().addFirst(nativeSerializer) + } + + isLegacy = false + } + + var disconnectionReason: String? = null + private set fun bind(channel: Channel) { - check(this.channel == null) { "Already having channel bound" } channel.config().setOption(ChannelOption.TCP_NODELAY, true) this.channel = channel - if (side == ConnectionSide.SERVER) - helloListener = HelloListener(this, channel) - } - - protected abstract fun onHelloReceived(helloPacket: HelloPacket) - protected abstract fun inGame() - - fun helloReceived(helloPacket: HelloPacket) { - LOGGER.info("Handshake received on $side from ${channel?.remoteAddress()}: $helloPacket") - otherSide = helloPacket - - if (side == ConnectionSide.SERVER) { - helloListener!!.sendHello(localUUID) - } - - onHelloReceived(helloPacket) - initializeHandlers() - } - - fun helloFailed() { - channel!!.close() - } - - fun initializeHandlers() { - val channel = channel ?: throw IllegalStateException("No network channel is bound") - - if (type == ConnectionType.NETWORK) { - channel.pipeline().addLast(PacketRegistry.NATIVE.Inbound(side)) - } else { - channel.pipeline().addLast(PacketRegistry.NATIVE.InboundValidator(side)) - } + if (type == ConnectionType.MEMORY) + channel.pipeline().addFirst(handshakeValidator) + else + channel.pipeline().addFirst(handshakeSerializer) channel.pipeline().addLast(this) - if (type == ConnectionType.NETWORK) { - channel.pipeline().addLast(PacketRegistry.NATIVE.Outbound(side)) - - channel.pipeline().addFirst(DatagramEncoder) - channel.pipeline().addFirst(DatagramDecoder()) - } else { - channel.pipeline().addLast(PacketRegistry.NATIVE.OutboundValidator(side)) + channel.closeFuture().addListener { + LOGGER.info("Connection to ${channel.remoteAddress()} is closed") } - - inGame() } + protected abstract fun inGame() + fun send(packet: IPacket) { - val channel = channel ?: throw IllegalStateException("No network channel is bound") channel.write(packet) } fun sendAndFlush(packet: IPacket) { - val channel = channel ?: throw IllegalStateException("No network channel is bound") channel.write(packet) channel.flush() } fun flush() { - val channel = channel ?: throw IllegalStateException("No network channel is bound") channel.flush() } fun disconnect(reason: String) { if (side == ConnectionSide.CLIENT) { - channel!!.close() + channel.close() } else { - channel!!.write(DisconnectPacket(reason)) - channel!!.flush() - channel!!.close() + channel.flush() + channel.close() } } + override fun close() { + channel.close() + } + companion object { private val EMPTY_UUID = UUID(0L, 0L) private val LOGGER = LogManager.getLogger() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt index e0046c93..7f5122c3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt @@ -3,26 +3,40 @@ package ru.dbotthepony.kstarbound.network import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufInputStream import io.netty.buffer.ByteBufOutputStream +import io.netty.channel.ChannelDuplexHandler import io.netty.channel.ChannelHandlerContext -import io.netty.channel.ChannelInboundHandlerAdapter -import io.netty.channel.ChannelOutboundHandlerAdapter import io.netty.channel.ChannelPromise +import it.unimi.dsi.fastutil.bytes.ByteArrayList +import it.unimi.dsi.fastutil.io.FastByteArrayInputStream +import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kommons.io.readSignedVarInt +import ru.dbotthepony.kommons.io.writeSignedVarInt import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket -import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket +import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientConnectPacket +import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket +import ru.dbotthepony.kstarbound.network.packets.clientbound.HandshakeChallengePacket +import ru.dbotthepony.kstarbound.network.packets.serverbound.HandshakeResponsePacket +import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket +import ru.dbotthepony.kstarbound.network.packets.ProtocolResponsePacket +import ru.dbotthepony.kstarbound.network.packets.clientbound.ServerDisconnectPacket import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket +import java.io.BufferedInputStream import java.io.DataInputStream import java.io.DataOutputStream +import java.io.InputStream +import java.util.zip.InflaterInputStream +import kotlin.math.absoluteValue import kotlin.reflect.KClass -class PacketRegistry { - private val packets = ArrayList>() +class PacketRegistry(val isLegacy: Boolean) { + private val packets = ArrayList?>() private val clazz2Type = Reference2ObjectOpenHashMap, Type<*>>() private data class Type(val id: Int, val type: KClass, val factory: IPacketReader, val direction: PacketDirection) @@ -47,34 +61,110 @@ class PacketRegistry { return add(T::class, reader, direction) } - inner class Inbound(val side: ConnectionSide) : ChannelInboundHandlerAdapter() { + private fun skip(amount: Int = 1) { + for (i in 0 until amount) { + packets.add(null) + } + } + + inner class Serializer(val side: ConnectionSide) : ChannelDuplexHandler() { + private val backlog = ByteArrayList() + private var discardBytes = 0 + private var readableBytes = 0 + private var isCompressed = false + private var readingType: Type<*>? = null + override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is ByteBuf) { - val packetType = msg.readUnsignedByte().toInt() - val type = packets.getOrNull(packetType) + try { + while (msg.readableBytes() > 0) { + if (discardBytes > 0) { + val toSkip = discardBytes.coerceAtMost(msg.readableBytes()) + discardBytes -= toSkip + msg.skipBytes(toSkip) + continue + } - if (type == null) { - LOGGER.error("Unknown packet type $packetType!") - msg.release() - } else if (!type.direction.acceptedOn(side)) { - LOGGER.error("Packet ${type.type} can not be accepted on side $side!") - msg.release() - } else { - try { - ctx.fireChannelRead(type.factory.read(DataInputStream(ByteBufInputStream(msg)))) - } catch (err: Throwable) { - LOGGER.error("Error while reading incoming packet from network", err) - } finally { - msg.release() + if (readingType != null) { + while (readableBytes > 0 && msg.readableBytes() > 0) { + backlog.add(msg.readByte()) + readableBytes-- + } + + if (readableBytes == 0) { + val stream: InputStream + + if (isCompressed) { + stream = BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(backlog.elements(), 0, backlog.size))) + } else { + stream = FastByteArrayInputStream(backlog.elements(), 0, backlog.size) + } + + try { + ctx.fireChannelRead(readingType!!.factory.read(DataInputStream(stream), isLegacy)) + } catch (err: Throwable) { + LOGGER.error("Error while reading incoming packet from network (type ${readingType!!.id}; ${readingType!!.type})", err) + } + + backlog.clear() + readingType = null + isCompressed = false + } + + continue + } + + val stream = DataInputStream(ByteBufInputStream(msg)) + val packetType = stream.readUnsignedByte() + val dataLength = stream.readSignedVarInt() + + val type = packets.getOrNull(packetType) + + if (type == null) { + LOGGER.error("Unknown packet type $packetType! Discarding ${dataLength.absoluteValue} bytes") + discardBytes = dataLength.absoluteValue + } else if (!type.direction.acceptedOn(side)) { + LOGGER.error("Packet type $packetType (${type.type}) can not be accepted on side $side! Discarding ${dataLength.absoluteValue} bytes") + discardBytes = dataLength.absoluteValue + } else { + readingType = type + readableBytes = dataLength.absoluteValue + isCompressed = dataLength < 0 + } } + } finally { + msg.release() } } else { super.channelRead(ctx, msg) } } + + override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { + val type = clazz2Type[msg::class] + + if (type == null) { + LOGGER.error("Unknown outgoing message type ${msg::class}, it will not reach the other side.") + } else if (!type.direction.acceptedOn(side.opposite)) { + LOGGER.error("Packet ${type.type} can not be accepted on side ${side.opposite}, refusing to send it!") + } else { + val stream = FastByteArrayOutputStream() + (msg as IPacket).write(DataOutputStream(stream), isLegacy) + + if (isLegacy) + check(stream.length > 0) { "Packet $msg didn't write any data to network, this is not allowed by legacy protocol" } + + val buff = ctx.alloc().buffer(stream.length + 5) + val stream2 = ByteBufOutputStream(buff) + stream2.writeByte(type.id) + stream2.writeSignedVarInt(stream.length) + stream2.write(stream.array, 0, stream.length) + ctx.write(buff, promise) + } + } } - inner class InboundValidator(val side: ConnectionSide) : ChannelInboundHandlerAdapter() { + inner class Validator(val side: ConnectionSide) : ChannelDuplexHandler() { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { val type = clazz2Type[msg::class] @@ -90,26 +180,7 @@ class PacketRegistry { } } } - } - inner class Outbound(val side: ConnectionSide) : ChannelOutboundHandlerAdapter() { - override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { - val type = clazz2Type[msg::class] - - if (type == null) { - LOGGER.error("Unknown outgoing message type ${msg::class}, it will not reach the other side.") - } else if (!type.direction.acceptedOn(side.opposite)) { - LOGGER.error("Packet ${type.type} can not be accepted on side ${side.opposite}, refusing to send it!") - } else { - val buff = ctx.alloc().buffer(2048) - buff.writeByte(type.id) - (msg as IPacket).write(DataOutputStream(ByteBufOutputStream(buff))) - ctx.write(buff, promise) - } - } - } - - inner class OutboundValidator(val side: ConnectionSide) : ChannelOutboundHandlerAdapter() { override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { val type = clazz2Type[msg::class] @@ -126,12 +197,11 @@ class PacketRegistry { companion object { private val LOGGER = LogManager.getLogger() - val NATIVE = PacketRegistry() - val LEGACY = PacketRegistry() - val HANDSHAKE = PacketRegistry() + val NATIVE = PacketRegistry(false) + val LEGACY = PacketRegistry(true) + val HANDSHAKE = PacketRegistry(false) init { - NATIVE.add(::DisconnectPacket) NATIVE.add(::JoinWorldPacket) NATIVE.add(::ChunkCellsPacket) NATIVE.add(::ForgetChunkPacket) @@ -139,6 +209,96 @@ class PacketRegistry { NATIVE.add(::TrackedSizePacket) NATIVE.add(::SpawnWorldObjectPacket) NATIVE.add(::ForgetEntityPacket) + + HANDSHAKE.add(::ProtocolRequestPacket) + HANDSHAKE.add(::ProtocolResponsePacket) + + LEGACY.skip() // ProtocolRequest + LEGACY.skip() // ProtocolResponse + + // Packets sent universe server -> universe client + LEGACY.add(::ServerDisconnectPacket) // ServerDisconnect + LEGACY.add(::ConnectSuccessPacket) // ConnectSuccess + LEGACY.skip() // ConnectFailure + LEGACY.add(::HandshakeChallengePacket) // HandshakeChallenge + LEGACY.skip() // ChatReceive + LEGACY.skip() // UniverseTimeUpdate + LEGACY.skip() // CelestialResponse + LEGACY.skip() // PlayerWarpResult + LEGACY.skip() // PlanetTypeUpdate + LEGACY.skip() // Pause + LEGACY.skip() // ServerInfo + + // Packets sent universe client -> universe server + LEGACY.add(::ClientConnectPacket) // ClientConnect + LEGACY.skip() // ClientDisconnectRequest + LEGACY.add(::HandshakeResponsePacket) // HandshakeResponse + LEGACY.skip() // PlayerWarp + LEGACY.skip() // FlyShip + LEGACY.skip() // ChatSend + LEGACY.skip() // CelestialRequest + + // Packets sent bidirectionally between the universe client and the universe + // server + LEGACY.skip() // ClientContextUpdate + + // Packets sent world server -> world client + LEGACY.skip() // WorldStart + LEGACY.skip() // WorldStop + LEGACY.skip() // WorldLayoutUpdate + LEGACY.skip() // WorldParametersUpdate + LEGACY.skip() // CentralStructureUpdate + LEGACY.skip() // TileArrayUpdate + LEGACY.skip() // TileUpdate + LEGACY.skip() // TileLiquidUpdate + LEGACY.skip() // TileDamageUpdate + LEGACY.skip() // TileModificationFailure + LEGACY.skip() // GiveItem + LEGACY.skip() // EnvironmentUpdate + LEGACY.skip() // UpdateTileProtection + LEGACY.skip() // SetDungeonGravity + LEGACY.skip() // SetDungeonBreathable + LEGACY.skip() // SetPlayerStart + LEGACY.skip() // FindUniqueEntityResponse + LEGACY.skip() // Pong + + // Packets sent world client -> world server + LEGACY.skip() // ModifyTileList + LEGACY.skip() // DamageTileGroup + LEGACY.skip() // CollectLiquid + LEGACY.skip() // RequestDrop + LEGACY.skip() // SpawnEntity + LEGACY.skip() // ConnectWire + LEGACY.skip() // DisconnectAllWires + LEGACY.skip() // WorldClientStateUpdate + LEGACY.skip() // FindUniqueEntity + LEGACY.skip() // WorldStartAcknowledge + LEGACY.skip() // Ping + + // Packets sent bidirectionally between world client and world server + LEGACY.skip() // EntityCreate + LEGACY.skip() // EntityUpdateSet + LEGACY.skip() // EntityDestroy + LEGACY.skip() // EntityInteract + LEGACY.skip() // EntityInteractResult + LEGACY.skip() // HitRequest + LEGACY.skip() // DamageRequest + LEGACY.skip() // DamageNotification + LEGACY.skip() // EntityMessage + LEGACY.skip() // EntityMessageResponse + LEGACY.skip() // UpdateWorldProperties + LEGACY.skip() // StepUpdate + + // Packets sent system server -> system client + LEGACY.skip() // SystemWorldStart + LEGACY.skip() // SystemWorldUpdate + LEGACY.skip() // SystemObjectCreate + LEGACY.skip() // SystemObjectDestroy + LEGACY.skip() // SystemShipCreate + LEGACY.skip() // SystemShipDestroy + + // Packets sent system client -> system server + LEGACY.skip() // SystemObjectSpawn } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/ProtocolRequestPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/ProtocolRequestPacket.kt new file mode 100644 index 00000000..b99320b0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/ProtocolRequestPacket.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.kstarbound.network.packets + +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation +import ru.dbotthepony.kstarbound.network.IServerPacket +import ru.dbotthepony.kstarbound.server.ServerConnection +import java.io.DataInputStream +import java.io.DataOutputStream +import java.util.UUID + +data class ProtocolRequestPacket(val version: Int) : IServerPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInt()) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeInt(version) + } + + override fun play(connection: ServerConnection) { + if (version == Starbound.NATIVE_PROTOCOL_VERSION) { + connection.sendAndFlush(ProtocolResponsePacket(true)) + connection.setupNative() + } else if (version == Starbound.LEGACY_PROTOCOL_VERSION) { + connection.sendAndFlush(ProtocolResponsePacket(true)) + connection.setupLegacy() + } else { + connection.sendAndFlush(ProtocolResponsePacket(false)) + connection.close() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/ProtocolResponsePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/ProtocolResponsePacket.kt new file mode 100644 index 00000000..9c3710bb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/ProtocolResponsePacket.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.kstarbound.network.packets + +import ru.dbotthepony.kstarbound.client.ClientConnection +import ru.dbotthepony.kstarbound.defs.player.ShipUpgrades +import ru.dbotthepony.kstarbound.network.IClientPacket +import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientConnectPacket +import java.io.DataInputStream +import java.io.DataOutputStream +import java.util.* +import kotlin.collections.HashMap + +data class ProtocolResponsePacket(val allowed: Boolean) : IClientPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBoolean()) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeBoolean(allowed) + } + + override fun play(connection: ClientConnection) { + if (allowed) { + if (connection.isLegacy) { + connection.setupLegacy() + connection.sendAndFlush( + ClientConnectPacket( + ByteArray(ClientConnectPacket.DIGEST_SIZE), + allowAssetMismatch = true, + playerUuid = UUID(0L, 0L), + playerName = "Test Name", + playerSpecies = "hylotl", + shipChunks = HashMap(), + shipUpgrades = ShipUpgrades(), + introComplete = false, + account = "" + ) + ) + } else { + connection.setupNative() + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt deleted file mode 100644 index a02b83c3..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt +++ /dev/null @@ -1,88 +0,0 @@ -package ru.dbotthepony.kstarbound.network.packets - -import io.netty.buffer.ByteBuf -import io.netty.buffer.ByteBufInputStream -import io.netty.buffer.ByteBufOutputStream -import io.netty.channel.Channel -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.ChannelInboundHandlerAdapter -import ru.dbotthepony.kommons.io.readUUID -import ru.dbotthepony.kommons.io.writeUUID -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.client.ClientConnection -import ru.dbotthepony.kstarbound.network.Connection -import ru.dbotthepony.kstarbound.network.IClientPacket -import ru.dbotthepony.kstarbound.network.IConnectionDetails -import ru.dbotthepony.kstarbound.network.IPacket -import ru.dbotthepony.kstarbound.network.IServerPacket -import ru.dbotthepony.kstarbound.network.readUTF -import ru.dbotthepony.kstarbound.network.writeUTF -import ru.dbotthepony.kstarbound.server.ServerConnection -import java.io.DataInputStream -import java.io.DataOutputStream -import java.util.UUID - -data class HelloPacket( - override val protocolVersion: Int, - override val engineVersion: String, - override val username: String, - override val password: String, - override val uuid: UUID -) : IPacket, IConnectionDetails { - constructor(buff: DataInputStream) : this(buff.readInt(), buff.readUTF(), buff.readUTF(), buff.readUTF(), buff.readUUID()) - - override fun write(stream: DataOutputStream) { - stream.writeInt(protocolVersion) - stream.writeUTF(engineVersion) - stream.writeUTF(username) - stream.writeUTF(password) - stream.writeUUID(uuid) - } -} - -data class DisconnectPacket(val reason: String) : IServerPacket, IClientPacket { - constructor(buff: DataInputStream) : this(buff.readUTF()) - - override fun write(stream: DataOutputStream) { - stream.writeUTF(reason) - } - - override fun play(connection: ServerConnection) { - - } - - override fun play(connection: ClientConnection) { - - } -} - -class HelloListener(val connection: Connection, private val channel: Channel) : ChannelInboundHandlerAdapter() { - init { - channel.pipeline().addFirst(this) - } - - fun sendHello(uuid: UUID, username: String = "", password: String = ""): HelloListener { - val buf = channel.config().allocator.buffer() - buf.writeUTF("KSTARBOUND HELLO") - HelloPacket(Starbound.NATIVE_PROTOCOL_VERSION, Starbound.ENGINE_VERSION, username, password, uuid).write(DataOutputStream(ByteBufOutputStream(buf))) - channel.write(buf) - channel.flush() - return this - } - - override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { - if (msg is ByteBuf) { - if (msg.readUTF() != "KSTARBOUND HELLO") { - connection.helloFailed() - } else { - connection.helloReceived(HelloPacket(DataInputStream(ByteBufInputStream(msg)))) - } - - msg.release() - } else { - connection.helloFailed() - } - - channel.pipeline().remove(this) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ConnectFailurePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ConnectFailurePacket.kt new file mode 100644 index 00000000..f0ea0985 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ConnectFailurePacket.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.network.packets.clientbound + +import ru.dbotthepony.kommons.io.readBinaryString +import ru.dbotthepony.kommons.io.writeBinaryString +import ru.dbotthepony.kstarbound.client.ClientConnection +import ru.dbotthepony.kstarbound.network.IClientPacket +import java.io.DataInputStream +import java.io.DataOutputStream + +class ConnectFailurePacket(val reason: String = "") : IClientPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBinaryString()) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeBinaryString(reason) + } + + override fun play(connection: ClientConnection) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ConnectSuccessPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ConnectSuccessPacket.kt new file mode 100644 index 00000000..1d8678a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ConnectSuccessPacket.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.kstarbound.network.packets.clientbound + +import ru.dbotthepony.kommons.io.readUUID +import ru.dbotthepony.kommons.io.writeUUID +import ru.dbotthepony.kstarbound.client.ClientConnection +import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation +import ru.dbotthepony.kstarbound.network.IClientPacket +import java.io.DataInputStream +import java.io.DataOutputStream +import java.util.UUID + +class ConnectSuccessPacket(val connectionID: Int, val serverUUID: UUID, val celestialInformation: CelestialBaseInformation) : IClientPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this( + stream.readUnsignedShort(), + stream.readUUID(), + CelestialBaseInformation(stream, isLegacy) + ) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeShort(connectionID) + stream.writeUUID(serverUUID) + celestialInformation.write(stream, isLegacy) + } + + override fun play(connection: ClientConnection) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/HandshakeChallengePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/HandshakeChallengePacket.kt new file mode 100644 index 00000000..5a9d017b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/HandshakeChallengePacket.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.network.packets.clientbound + +import ru.dbotthepony.kommons.io.readByteArray +import ru.dbotthepony.kommons.io.writeByteArray +import ru.dbotthepony.kstarbound.client.ClientConnection +import ru.dbotthepony.kstarbound.network.IClientPacket +import java.io.DataInputStream +import java.io.DataOutputStream + +class HandshakeChallengePacket(val passwordSalt: ByteArray) : IClientPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readByteArray()) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeByteArray(passwordSalt) + } + + override fun play(connection: ClientConnection) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ServerDisconnectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ServerDisconnectPacket.kt new file mode 100644 index 00000000..611e32b2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/clientbound/ServerDisconnectPacket.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.network.packets.clientbound + +import ru.dbotthepony.kommons.io.readBinaryString +import ru.dbotthepony.kommons.io.writeBinaryString +import ru.dbotthepony.kstarbound.client.ClientConnection +import ru.dbotthepony.kstarbound.network.IClientPacket +import java.io.DataInputStream +import java.io.DataOutputStream + +class ServerDisconnectPacket(val reason: String = "") : IClientPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readBinaryString()) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeBinaryString(reason) + } + + override fun play(connection: ClientConnection) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt new file mode 100644 index 00000000..d41df676 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/ClientConnectPacket.kt @@ -0,0 +1,68 @@ +package ru.dbotthepony.kstarbound.network.packets.serverbound + +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kommons.io.readBinaryString +import ru.dbotthepony.kommons.io.readByteArray +import ru.dbotthepony.kommons.io.readKOptional +import ru.dbotthepony.kommons.io.readMap +import ru.dbotthepony.kommons.io.readUUID +import ru.dbotthepony.kommons.io.writeBinaryString +import ru.dbotthepony.kommons.io.writeByteArray +import ru.dbotthepony.kommons.io.writeKOptional +import ru.dbotthepony.kommons.io.writeMap +import ru.dbotthepony.kommons.io.writeUUID +import ru.dbotthepony.kommons.util.KOptional +import ru.dbotthepony.kstarbound.defs.CelestialBaseInformation +import ru.dbotthepony.kstarbound.defs.player.ShipUpgrades +import ru.dbotthepony.kstarbound.io.ByteKey +import ru.dbotthepony.kstarbound.io.readByteKey +import ru.dbotthepony.kstarbound.io.writeByteKey +import ru.dbotthepony.kstarbound.network.IServerPacket +import ru.dbotthepony.kstarbound.network.packets.clientbound.ConnectSuccessPacket +import ru.dbotthepony.kstarbound.server.ServerConnection +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.InputStream +import java.util.* +import kotlin.collections.HashMap + +data class ClientConnectPacket( + val assetDigest: ByteArray, val allowAssetMismatch: Boolean, + val playerUuid: UUID, val playerName: String, val playerSpecies: String, + val shipChunks: Map>, val shipUpgrades: ShipUpgrades, val introComplete: Boolean, + val account: String +) : IServerPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this( + stream.readByteArray(), + stream.readBoolean(), + stream.readUUID(), + stream.readBinaryString(), + stream.readBinaryString(), + stream.readMap(InputStream::readByteKey, { readKOptional { readByteArray() } }, { HashMap(it) }), + ShipUpgrades.read(stream, isLegacy), + stream.readBoolean(), + stream.readBinaryString() + ) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeByteArray(assetDigest) + stream.writeBoolean(allowAssetMismatch) + stream.writeUUID(playerUuid) + stream.writeBinaryString(playerName) + stream.writeBinaryString(playerSpecies) + stream.writeMap(shipChunks, { writeByteKey(it) }, { writeKOptional(it) { writeByteArray(it) } }) + shipUpgrades.write(stream, isLegacy) + stream.writeBoolean(introComplete) + stream.writeBinaryString(account) + } + + override fun play(connection: ServerConnection) { + LOGGER.info("Client connection request received from ${connection.channel.remoteAddress()}, Player $playerName/$playerUuid (account '$account')") + connection.sendAndFlush(ConnectSuccessPacket(4, UUID(4L, 4L), CelestialBaseInformation())) + } + + companion object { + const val DIGEST_SIZE = 32 + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/HandshakeResponsePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/HandshakeResponsePacket.kt new file mode 100644 index 00000000..87a215c1 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/serverbound/HandshakeResponsePacket.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.network.packets.serverbound + +import ru.dbotthepony.kommons.io.readByteArray +import ru.dbotthepony.kommons.io.writeByteArray +import ru.dbotthepony.kstarbound.network.IServerPacket +import ru.dbotthepony.kstarbound.server.ServerConnection +import java.io.DataInputStream +import java.io.DataOutputStream + +class HandshakeResponsePacket(val passwordHash: ByteArray) : IServerPacket { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readByteArray()) + + override fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeByteArray(passwordHash) + } + + override fun play(connection: ServerConnection) { + TODO("Not yet implemented") + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerChannels.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerChannels.kt index 910a81d5..2a11cc21 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerChannels.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerChannels.kt @@ -41,9 +41,15 @@ class ServerChannels(val server: StarboundServer) : Closeable { val channel = ServerBootstrap().channel(LocalServerChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer() { override fun initChannel(ch: Channel) { LOGGER.info("Incoming connection from ${ch.remoteAddress()}") - val connection = ServerConnection(server, ConnectionType.MEMORY) - connections.add(connection) - connection.bind(ch) + + try { + val connection = ServerConnection(server, ConnectionType.MEMORY) + connections.add(connection) + connection.bind(ch) + } catch (err: Throwable) { + LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err) + ch.close() + } } }).bind(LocalAddress.ANY).syncUninterruptibly() @@ -64,9 +70,15 @@ class ServerChannels(val server: StarboundServer) : Closeable { val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer() { override fun initChannel(ch: Channel) { LOGGER.info("Incoming connection from ${ch.remoteAddress()}") - val connection = ServerConnection(server, ConnectionType.NETWORK) - connections.add(connection) - connection.bind(ch) + + try { + val connection = ServerConnection(server, ConnectionType.NETWORK) + connections.add(connection) + connection.bind(ch) + } catch (err: Throwable) { + LOGGER.error("Error while accepting new connection from ${ch.remoteAddress()}", err) + ch.close() + } } }).bind(localAddress).syncUninterruptibly() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt index ae67758c..b8d10333 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt @@ -14,7 +14,6 @@ import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.ConnectionSide import ru.dbotthepony.kstarbound.network.ConnectionType import ru.dbotthepony.kstarbound.network.IServerPacket -import ru.dbotthepony.kstarbound.network.packets.HelloPacket import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.IChunkListener @@ -115,7 +114,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn } fun tick() { - channel?.flush() + channel.flush() val world = world if (world == null) { @@ -154,9 +153,6 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn server.playerInGame(this) } - override fun onHelloReceived(helloPacket: HelloPacket) { - } - companion object { private val LOGGER = LogManager.getLogger() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt index c07590c5..35c899d7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt @@ -9,9 +9,9 @@ import java.io.DataInputStream import java.io.DataOutputStream data class TrackedPositionPacket(val pos: Vector2d) : IServerPacket { - constructor(stream: DataInputStream) : this(stream.readVec2d()) + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVec2d()) - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeVec2d(pos) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt index a7fa0344..47d086ba 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt @@ -6,14 +6,14 @@ import java.io.DataInputStream import java.io.DataOutputStream data class TrackedSizePacket(val width: Int, val height: Int) : IServerPacket { - constructor(stream: DataInputStream) : this(stream.readUnsignedByte(), stream.readUnsignedByte()) + constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readUnsignedByte(), stream.readUnsignedByte()) init { require(width in 1 .. 12) { "Bad chunk width to track: $width" } require(height in 1 .. 12) { "Bad chunk height to track: $height" } } - override fun write(stream: DataOutputStream) { + override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.writeByte(width) stream.writeByte(height) }