package ru.dbotthepony.kstarbound.client import io.netty.bootstrap.Bootstrap import io.netty.channel.Channel import io.netty.channel.ChannelHandlerContext import io.netty.channel.ChannelInitializer 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.kommons.util.KOptional 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.ClientContextUpdatePacket import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket import java.net.SocketAddress import java.util.* // clientside part of connection class ClientConnection(val client: StarboundClient, type: ConnectionType) : Connection(ConnectionSide.CLIENT, type) { private fun sendHello(asLegacy: Boolean = false) { isLegacy = asLegacy if (asLegacy) { channel.write(ProtocolRequestPacket(Starbound.LEGACY_PROTOCOL_VERSION)) } else { channel.write(ProtocolRequestPacket(Starbound.NATIVE_PROTOCOL_VERSION)) } channel.flush() } fun enqueue(task: StarboundClient.() -> Unit) { client.mailbox.execute { task.invoke(client) } } override fun inGame() { } override fun toString(): String { val channel = if (hasChannel) channel.remoteAddress().toString() else "" return "ClientConnection[ID=$connectionID channel=$channel]" } override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is IClientPacket) { try { msg.play(this) } catch (err: Throwable) { LOGGER.error("Failed to read incoming packet $msg", err) disconnect(err.toString()) } } else { LOGGER.error("Unknown incoming packet type $msg") disconnect("Unknown incoming packet type $msg") } } private var clientStateNetVersion = 0L override fun flush() { if (!pendingDisconnect && isConnected) { val entries = rpc.write() if (entries != null) { channel.write(ClientContextUpdatePacket(entries, KOptional(), KOptional())) } val (data, new) = clientStateGroup.write(clientStateNetVersion) if (data.isNotEmpty()) channel.write(WorldClientStateUpdatePacket(data)) clientStateNetVersion = new } super.flush() } private var pendingDisconnect = false fun disconnectNow() { pendingDisconnect = false if (channel.isOpen) { channel.close() } } override fun disconnect(reason: String) { if (pendingDisconnect) return if (channel.isOpen) { pendingDisconnect = true sendAndFlush(ClientDisconnectRequestPacket) } else { disconnectNow() } } override fun onChannelClosed() { super.onChannelClosed() if (pendingDisconnect) { disconnectNow() } } fun bootstrap(address: SocketAddress = channel.remoteAddress(), asLegacy: Boolean = false) { LOGGER.info("Trying to connect to remote server at $address with ${if (asLegacy) "legacy" else "native"} protocol") Bootstrap() .group(NIO_POOL) .channel(NioSocketChannel::class.java) .handler(object : ChannelInitializer() { override fun initChannel(ch: Channel) { bind(ch) } }) .connect(address) .syncUninterruptibly() sendHello(asLegacy) } companion object { private val LOGGER = LogManager.getLogger() fun connectToLocalServer(client: StarboundClient, address: LocalAddress): ClientConnection { LOGGER.info("Trying to connect to local server at $address") val connection = ClientConnection(client, ConnectionType.MEMORY) Bootstrap() .group(NIO_POOL) .channel(LocalChannel::class.java) .handler(object : ChannelInitializer() { override fun initChannel(ch: Channel) { connection.bind(ch) } }) .connect(address) .syncUninterruptibly() connection.sendHello() return connection } fun connectToLocalServer(client: StarboundClient, address: Channel): ClientConnection { return connectToLocalServer(client, address.localAddress() as LocalAddress) } fun connectToRemoteServer(client: StarboundClient, address: SocketAddress): ClientConnection { val connection = ClientConnection(client, ConnectionType.NETWORK) connection.bootstrap(address) return connection } } }