From a2285a5a032787a4fa68a57e53a7c2edf1eb2499 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Sun, 1 Aug 2021 14:04:24 +0200 Subject: [PATCH] psoserv game servers now use the same infrastructure as the proxy server. --- .../kotlin/world/phantasmal/psoserv/Main.kt | 44 ++-- .../phantasmal/psoserv/servers/BbServer.kt | 21 +- .../phantasmal/psoserv/servers/GameServer.kt | 68 ++++++ .../phantasmal/psoserv/servers/ProxyServer.kt | 105 ++-------- .../phantasmal/psoserv/servers/Server.kt | 194 +++--------------- .../phantasmal/psoserv/servers/ServerState.kt | 26 ++- .../psoserv/servers/SocketHandler.kt | 45 +++- .../psoserv/servers/SocketSender.kt | 7 + .../psoserv/servers/character/DataServer.kt | 23 ++- .../psoserv/servers/character/DataState.kt | 25 ++- .../psoserv/servers/login/LoginServer.kt | 23 ++- .../psoserv/servers/login/LoginState.kt | 21 +- .../psoserv/servers/patch/PatchServer.kt | 36 ++-- .../psoserv/servers/patch/PatchState.kt | 25 ++- 14 files changed, 295 insertions(+), 368 deletions(-) create mode 100644 psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt create mode 100644 psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt index 576d79b1..74e5d8e1 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt @@ -70,16 +70,16 @@ fun main(args: Array) { } private class PhantasmalServer( - private val servers: List>, + private val servers: List, private val proxyServers: List, ) { fun start() { - servers.forEach(Server<*, *>::start) + servers.forEach(Server::start) proxyServers.forEach(ProxyServer::start) } fun stop() { - servers.forEach(Server<*, *>::stop) + servers.forEach(Server::stop) proxyServers.forEach(ProxyServer::stop) } } @@ -90,36 +90,40 @@ private fun initialize(config: Config): PhantasmalServer { val dataAddress = config.data?.address?.let(::inet4Address) ?: defaultAddress val dataPort = config.data?.port ?: DEFAULT_DATA_PORT - val servers = mutableListOf>() + val servers = mutableListOf() // If no proxy config is specified, we run a regular PSO server by default. val run = config.proxy == null || !config.proxy.run if (config.patch == null && run || config.patch?.run == true) { - val address = config.patch?.address?.let(::inet4Address) ?: defaultAddress - val port = config.patch?.port ?: DEFAULT_PATCH_PORT + val bindPair = Inet4Pair( + config.patch?.address?.let(::inet4Address) ?: defaultAddress, + config.patch?.port ?: DEFAULT_PATCH_PORT, + ) - LOGGER.info { "Configuring patch server to bind to $address:$port." } + LOGGER.info { "Configuring patch server to bind to $bindPair." } servers.add( PatchServer( - address, - port, + name = "patch", + bindPair, welcomeMessage = config.patch?.welcomeMessage ?: "Welcome to Phantasmal World.", ) ) } if (config.login == null && run || config.login?.run == true) { - val address = config.login?.address?.let(::inet4Address) ?: defaultAddress - val port = config.login?.port ?: DEFAULT_LOGIN_PORT + val bindPair = Inet4Pair( + config.login?.address?.let(::inet4Address) ?: defaultAddress, + config.login?.port ?: DEFAULT_LOGIN_PORT, + ) - LOGGER.info { "Configuring login server to bind to $address:$port." } + LOGGER.info { "Configuring login server to bind to $bindPair." } servers.add( LoginServer( - address, - port, + name = "login", + bindPair, dataServerAddress = dataAddress, dataServerPort = dataPort, ) @@ -127,15 +131,17 @@ private fun initialize(config: Config): PhantasmalServer { } if (config.data == null && run || config.data?.run == true) { - val address = config.data?.address?.let(::inet4Address) ?: defaultAddress - val port = config.data?.port ?: DEFAULT_DATA_PORT + val bindPair = Inet4Pair( + config.data?.address?.let(::inet4Address) ?: defaultAddress, + config.data?.port ?: DEFAULT_DATA_PORT, + ) - LOGGER.info { "Configuring data server to bind to $address:$port." } + LOGGER.info { "Configuring data server to bind to $bindPair." } servers.add( DataServer( - address, - port, + name = "data", + bindPair, ) ) } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt index 005a4887..5390ffd9 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt @@ -1,24 +1,15 @@ package world.phantasmal.psoserv.servers -import mu.KLogger -import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psoserv.encryption.BbCipher import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.messages.BbMessageDescriptor -import world.phantasmal.psoserv.messages.Header -import java.net.InetAddress -abstract class BbServer>( - logger: KLogger, - address: InetAddress, - port: Int, -) : Server(logger, address, port) { +abstract class BbServer>( + name: String, + bindPair: Inet4Pair, +) : GameServer(name, bindPair) { + + override val messageDescriptor = BbMessageDescriptor override fun createCipher() = BbCipher() - - override fun readHeader(buffer: Buffer): Header = - BbMessageDescriptor.readHeader(buffer) - - override fun readMessage(buffer: Buffer): BbMessage = - BbMessageDescriptor.readMessage(buffer) } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt new file mode 100644 index 00000000..0ef06090 --- /dev/null +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt @@ -0,0 +1,68 @@ +package world.phantasmal.psoserv.servers + +import world.phantasmal.psoserv.encryption.Cipher +import world.phantasmal.psoserv.messages.Message +import world.phantasmal.psoserv.messages.MessageDescriptor +import java.net.Socket + +abstract class GameServer( + name: String, + bindPair: Inet4Pair, +) : Server(name, bindPair) + where MessageType : Message, + StateType : ServerState { + + private var connectionCounter = 0 + + protected abstract val messageDescriptor: MessageDescriptor + + override fun clientConnected(clientSocket: Socket) { + // Handle each client connection in its own thread. + val thread = Thread { + val handler = ClientHandler(clientSocket) + handler.initializeState() + handler.listen() + } + thread.name = "${name}_client_${connectionCounter++}" + thread.start() + } + + protected abstract fun createCipher(): Cipher + + protected abstract fun initializeState( + sender: SocketSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): StateType + + private inner class ClientHandler(socket: Socket) : SocketHandler(logger, socket) { + private val serverCipher = createCipher() + private val clientCipher = createCipher() + + private var state: StateType? = null + + override val messageDescriptor = this@GameServer.messageDescriptor + + override val readDecryptCipher = clientCipher + override val readEncryptCipher: Cipher? = null + override val writeEncryptCipher: Cipher = serverCipher + + fun initializeState() { + state = initializeState(this, serverCipher, clientCipher) + } + + override fun processMessage(message: MessageType): ProcessResult { + state = state!!.process(message) + + return if (state is FinalServerState) { + // Give the client some time to disconnect. + Thread.sleep(100) + + // Close the connection. + ProcessResult.Done + } else { + ProcessResult.Ok + } + } + } +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ProxyServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ProxyServer.kt index 34fe0f1c..c054bafb 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ProxyServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ProxyServer.kt @@ -1,100 +1,31 @@ package world.phantasmal.psoserv.servers -import mu.KotlinLogging import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.messages.InitEncryptionMessage import world.phantasmal.psoserv.messages.Message import world.phantasmal.psoserv.messages.MessageDescriptor import world.phantasmal.psoserv.messages.RedirectMessage -import java.net.ServerSocket import java.net.Socket -import java.net.SocketException -import java.net.SocketTimeoutException class ProxyServer( - private val name: String, - private val bindPair: Inet4Pair, + name: String, + bindPair: Inet4Pair, private val remotePair: Inet4Pair, private val messageDescriptor: MessageDescriptor, private val createCipher: (key: ByteArray) -> Cipher, private val redirectMap: Map = emptyMap(), -) { - private val logger = KotlinLogging.logger(name) - private val proxySocket = ServerSocket() +) : Server(name, bindPair) { - @Volatile - private var running = false - - fun start() { - logger.info { "Starting." } - - proxySocket.bind(bindPair) - - running = true - - // Accept client connections on a dedicated thread. - val thread = Thread(::acceptConnections) - thread.name = name - thread.start() - } - - fun stop() { - logger.info { "Stopping." } - - // Signal to the connection thread that it should stop. - running = false - - // Closing the server socket will generate a SocketException on the connection thread which - // will then shut down. - proxySocket.close() - } - - private fun acceptConnections() { - if (running) { - logger.info { "Accepting connections." } - - while (running) { - try { - val clientSocket = proxySocket.accept() - logger.info { - "New client connection from ${clientSocket.inetAddress}:${clientSocket.port}." - } - - val serverSocket = Socket(remotePair.address, remotePair.port) - logger.info { - "Connected to server ${serverSocket.inetAddress}:${serverSocket.port}." - } - - // Listen to server on this thread. - // Don't start listening to the client until encryption is initialized. - ServerHandler(serverSocket, clientSocket).listen() - } catch (e: SocketTimeoutException) { - // Retry after timeout. - continue - } catch (e: InterruptedException) { - logger.error(e) { - "Interrupted while trying to accept client connections on $bindPair, stopping." - } - break - } catch (e: SocketException) { - // Don't log if we're not running anymore because that means this exception was - // probably generated by a socket.close() call. - if (running) { - logger.error(e) { - "Exception while trying to accept client connections on $bindPair, stopping." - } - } - break - } catch (e: Throwable) { - logger.error(e) { - "Exception while trying to accept client connections on $bindPair." - } - } - } + override fun clientConnected(clientSocket: Socket) { + val serverSocket = Socket(remotePair.address, remotePair.port) + logger.info { + "Connected to server ${serverSocket.inetAddress}:${serverSocket.port}." } - logger.info { "Stopped." } + // Listen to server on this thread. + // Don't start listening to the client until encryption is initialized. + ServerHandler(serverSocket, clientSocket).listen() } private inner class ServerHandler( @@ -109,14 +40,15 @@ class ProxyServer( // The first message sent by the server is always unencrypted and initializes the // encryption. We don't start listening to the client until the encryption is // initialized. - override var decryptCipher: Cipher? = null - override var encryptCipher: Cipher? = null + override var readDecryptCipher: Cipher? = null + override var readEncryptCipher: Cipher? = null + override val writeEncryptCipher: Cipher? = null override fun processMessage(message: Message): ProcessResult { when (message) { - is InitEncryptionMessage -> if (decryptCipher == null) { - decryptCipher = createCipher(message.serverKey) - encryptCipher = createCipher(message.serverKey) + is InitEncryptionMessage -> if (readDecryptCipher == null) { + readDecryptCipher = createCipher(message.serverKey) + readEncryptCipher = createCipher(message.serverKey) val clientDecryptCipher = createCipher(message.clientKey) val clientEncryptCipher = createCipher(message.clientKey) @@ -170,11 +102,12 @@ class ProxyServer( private inner class ClientHandler( clientSocket: Socket, private val serverHandler: ServerHandler, - override val decryptCipher: Cipher, - override val encryptCipher: Cipher, + override val readDecryptCipher: Cipher, + override val readEncryptCipher: Cipher, ) : SocketHandler(logger, clientSocket) { override val messageDescriptor = this@ProxyServer.messageDescriptor + override val writeEncryptCipher: Cipher? = null override fun processMessage(message: Message): ProcessResult = ProcessResult.Ok diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/Server.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/Server.kt index e7647b55..c0070d35 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/Server.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/Server.kt @@ -1,34 +1,32 @@ package world.phantasmal.psoserv.servers -import mu.KLogger -import world.phantasmal.psolib.Endianness -import world.phantasmal.psolib.buffer.Buffer -import world.phantasmal.psoserv.encryption.Cipher -import world.phantasmal.psoserv.messages.Header -import world.phantasmal.psoserv.messages.Message -import world.phantasmal.psoserv.messages.messageString -import world.phantasmal.psoserv.roundToBlockSize -import java.net.* +import mu.KotlinLogging +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketException +import java.net.SocketTimeoutException -private const val MAX_MSG_SIZE: Int = 32768 - -abstract class Server>( - private val logger: KLogger, - private val address: InetAddress, - private val port: Int, +abstract class Server( + protected val name: String, + private val bindPair: Inet4Pair, ) { - private val serverSocket = ServerSocket(port, 50, address) - private var connectionCounter = 0 + private val bindSocket = ServerSocket() @Volatile - private var running = true + private var running = false + + protected val logger = KotlinLogging.logger(name) fun start() { logger.info { "Starting." } + bindSocket.bind(bindPair) + + running = true + // Accept client connections on a dedicated thread. val thread = Thread(::acceptConnections) - thread.name = this::class.simpleName + thread.name = name thread.start() } @@ -40,7 +38,8 @@ abstract class Server= clientCipher.blockSize) { - if (!headerDecrypted) { - clientCipher.decrypt(readBuffer, offset = 0, blocks = 1) - headerDecrypted = true - } - - // Header size is always equal to cipher block size, so we can read it at this - // point. - val (code, size) = readHeader(readBuffer) - - when { - size > MAX_MSG_SIZE -> { - logger.error { - val message = messageString(code, size) - "Receiving $message, too large: ${size}B." - } - break - } - - read >= size -> { - val messageBuffer = readBuffer.copy(size = size) - - read -= size - maxRead = MAX_MSG_SIZE - read - headerDecrypted = false - - // Shift any remaining bytes to the front of the buffer. - if (read > 0) { - readBuffer.copyInto(readBuffer, offset = size, size = read) - } - - clientCipher.decrypt( - messageBuffer, - offset = clientCipher.blockSize, - blocks = size / clientCipher.blockSize - 1, - ) - - val message = readMessage(messageBuffer) - logger.trace { "Received $message." } - - state = state.process(message) - - if (state is FinalServerState) { - // Give the client some time to disconnect. - Thread.sleep(100) - - // Close the connection. - break - } - } - - else -> { - maxRead = size - read - } - } - } - } - } catch (e: Throwable) { - logger.error(e) { - "Error while processing client connection from ${socket.inetAddress}." - } - } finally { - logger.info { "Closing client connection from ${socket.inetAddress}." } - socket.close() - } - } - - protected abstract fun createCipher(): Cipher - - protected abstract fun initializeState(sender: ClientSender): StateType - - protected abstract fun readHeader(buffer: Buffer): Header - - protected abstract fun readMessage(buffer: Buffer): MessageType - - class ClientSender( - private val logger: KLogger, - private val socket: Socket, - val serverCipher: Cipher, - val clientCipher: Cipher, - ) { - fun send(message: Message, encrypt: Boolean = true) { - logger.trace { - "Sending $message${if (encrypt) "" else " (unencrypted)"}." - } - - if (message.buffer.size != message.size) { - logger.warn { - "Message size of $message is ${message.size}B, but wrote ${message.buffer.size} bytes." - } - } - - val buffer: Buffer - - if (encrypt) { - // Pad buffer before encrypting. - val initialSize = message.buffer.size - buffer = message.buffer.copy( - size = roundToBlockSize(initialSize, serverCipher.blockSize) - ) - serverCipher.encrypt(buffer) - } else { - buffer = message.buffer - } - - socket.getOutputStream().write(buffer.byteArray, 0, buffer.size) - } - } + protected abstract fun clientConnected(clientSocket: Socket) } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt index 65e21456..bb37a4fe 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt @@ -1,19 +1,31 @@ package world.phantasmal.psoserv.servers -import mu.KotlinLogging +import mu.KLogger import world.phantasmal.psoserv.messages.Message -abstract class ServerState> { - private val logger = KotlinLogging.logger(this::class.qualifiedName!!) +abstract class ServerStateContext( + val logger: KLogger, + private val socketSender: SocketSender, +) { + fun send(message: MessageType, encrypt: Boolean = true) { + socketSender.sendMessage(message, encrypt) + } +} + +abstract class ServerState( + protected val ctx: ContextType, +) where MessageType : Message, + ContextType : ServerStateContext, + Self : ServerState { init { - logger.trace { "Transitioning to ${this::class.simpleName}." } + ctx.logger.debug { "Transitioning to ${this::class.simpleName} state." } } - abstract fun process(message: ClientMsgType): Self + abstract fun process(message: MessageType): Self - protected fun unexpectedMessage(message: ClientMsgType): Self { - logger.debug { "Unexpected message: $message." } + protected fun unexpectedMessage(message: MessageType): Self { + ctx.logger.debug { "Unexpected message: $message." } @Suppress("UNCHECKED_CAST") return this as Self } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt index 989a02dd..103435ee 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt @@ -15,7 +15,7 @@ import kotlin.math.min abstract class SocketHandler( protected val logger: KLogger, private val socket: Socket, -) { +) : SocketSender { private val sockName: String = "${socket.remoteSocketAddress}" private val headerSize: Int get() = messageDescriptor.headerSize @@ -23,8 +23,14 @@ abstract class SocketHandler( private var running = false protected abstract val messageDescriptor: MessageDescriptor - protected abstract val decryptCipher: Cipher? - protected abstract val encryptCipher: Cipher? + protected abstract val readDecryptCipher: Cipher? + + /** + * Used by proxy servers to re-encrypt changed messages before sending them to the client. + */ + protected abstract val readEncryptCipher: Cipher? + + protected abstract val writeEncryptCipher: Cipher? fun listen() { logger.info { "Listening to $sockName." } @@ -50,8 +56,8 @@ abstract class SocketHandler( bufferLoop@ while (offset + headerSize <= readBuffer.size) { // Remember the current cipher in a local variable because processing a message // might change it. - val decryptCipher = decryptCipher - val encryptCipher = encryptCipher + val decryptCipher = readDecryptCipher + val encryptCipher = readEncryptCipher // Read header. readBuffer.copyInto(headerBuffer, offset = offset, size = headerSize) @@ -217,6 +223,35 @@ abstract class SocketHandler( socket.close() } + override fun sendMessage(message: MessageType, encrypt: Boolean) { + logger.trace { + "Sending $message${if (encrypt) "" else " (unencrypted)"}." + } + + if (message.buffer.size != message.size) { + logger.warn { + "Message size of $message is ${message.size}B, but wrote ${message.buffer.size} bytes." + } + } + + val cipher = writeEncryptCipher + val buffer: Buffer + + if (encrypt) { + checkNotNull(cipher) + // Pad buffer before encrypting. + val initialSize = message.buffer.size + buffer = message.buffer.copy( + size = roundToBlockSize(initialSize, cipher.blockSize) + ) + cipher.encrypt(buffer) + } else { + buffer = message.buffer + } + + socket.write(buffer, 0, buffer.size) + } + protected abstract fun processMessage(message: MessageType): ProcessResult protected open fun processRawBytes(buffer: Buffer, offset: Int, size: Int) { diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt new file mode 100644 index 00000000..820f8f26 --- /dev/null +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt @@ -0,0 +1,7 @@ +package world.phantasmal.psoserv.servers + +import world.phantasmal.psoserv.messages.Message + +interface SocketSender { + fun sendMessage(message: MessageType, encrypt: Boolean = true) +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataServer.kt index 20bdfca5..c4b77f7a 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataServer.kt @@ -1,21 +1,28 @@ package world.phantasmal.psoserv.servers.character -import mu.KotlinLogging +import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.servers.BbServer -import java.net.InetAddress +import world.phantasmal.psoserv.servers.Inet4Pair +import world.phantasmal.psoserv.servers.SocketSender -class DataServer(address: InetAddress, port: Int) : - BbServer(KotlinLogging.logger {}, address, port) { +class DataServer( + name: String, + bindPair: Inet4Pair, +) : BbServer(name, bindPair) { - override fun initializeState(sender: ClientSender): DataState { - val ctx = DataContext(sender) + override fun initializeState( + sender: SocketSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): DataState { + val ctx = DataContext(logger, sender) ctx.send( BbMessage.InitEncryption( "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", - sender.serverCipher.key, - sender.clientCipher.key, + serverCipher.key, + clientCipher.key, ), encrypt = false, ) diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataState.kt index cfbf1049..674acb31 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataState.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/character/DataState.kt @@ -1,5 +1,6 @@ package world.phantasmal.psoserv.servers.character +import mu.KLogger import world.phantasmal.core.math.clamp import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psolib.cursor.cursor @@ -7,20 +8,18 @@ import world.phantasmal.psoserv.messages.BbAuthenticationStatus import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.messages.PsoCharacter import world.phantasmal.psoserv.servers.FinalServerState -import world.phantasmal.psoserv.servers.Server import world.phantasmal.psoserv.servers.ServerState +import world.phantasmal.psoserv.servers.ServerStateContext +import world.phantasmal.psoserv.servers.SocketSender import kotlin.math.min class DataContext( - private val sender: Server.ClientSender, -) { - fun send(message: BbMessage, encrypt: Boolean = true) { - sender.send(message, encrypt) - } -} + logger: KLogger, + socketSender: SocketSender, +) : ServerStateContext(logger, socketSender) -sealed class DataState : ServerState() { - class Authentication(private val ctx: DataContext) : DataState() { +sealed class DataState(ctx: DataContext) : ServerState(ctx) { + class Authentication(ctx: DataContext) : DataState(ctx) { override fun process(message: BbMessage): DataState = if (message is BbMessage.Authenticate) { // TODO: Actual authentication. @@ -38,7 +37,7 @@ sealed class DataState : ServerState() { } } - class Account(private val ctx: DataContext) : DataState() { + class Account(ctx: DataContext) : DataState(ctx) { override fun process(message: BbMessage): DataState = if (message is BbMessage.GetAccount) { // TODO: Send correct guild card number and team ID. @@ -50,7 +49,7 @@ sealed class DataState : ServerState() { } } - class CharacterSelect(private val ctx: DataContext) : DataState() { + class CharacterSelect(ctx: DataContext) : DataState(ctx) { override fun process(message: BbMessage): DataState = when (message) { is BbMessage.CharacterSelect -> { @@ -97,7 +96,7 @@ sealed class DataState : ServerState() { } } - class DataDownload(private val ctx: DataContext) : DataState() { + class DataDownload(ctx: DataContext) : DataState(ctx) { private val guildCardBuffer = Buffer.withSize(54672) private val fileBuffer = Buffer.withSize(0) private var fileChunkNo = 0 @@ -178,7 +177,7 @@ sealed class DataState : ServerState() { } } - object Final : DataState(), FinalServerState { + class Final(ctx: DataContext) : DataState(ctx), FinalServerState { override fun process(message: BbMessage): DataState = unexpectedMessage(message) } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginServer.kt index 07687a4a..56b1eb31 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginServer.kt @@ -1,26 +1,31 @@ package world.phantasmal.psoserv.servers.login -import mu.KotlinLogging +import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.servers.BbServer +import world.phantasmal.psoserv.servers.Inet4Pair +import world.phantasmal.psoserv.servers.SocketSender import java.net.Inet4Address -import java.net.InetAddress class LoginServer( - address: InetAddress, - port: Int, + name: String, + bindPair: Inet4Pair, private val dataServerAddress: Inet4Address, private val dataServerPort: Int, -) : BbServer(KotlinLogging.logger {}, address, port) { +) : BbServer(name, bindPair) { - override fun initializeState(sender: ClientSender): LoginState { - val ctx = LoginContext(sender, dataServerAddress.address, dataServerPort) + override fun initializeState( + sender: SocketSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): LoginState { + val ctx = LoginContext(logger, sender, dataServerAddress.address, dataServerPort) ctx.send( BbMessage.InitEncryption( "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", - sender.serverCipher.key, - sender.clientCipher.key, + serverCipher.key, + clientCipher.key, ), encrypt = false, ) diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginState.kt index 2e37e1cb..2b7dea3c 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginState.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/login/LoginState.kt @@ -1,23 +1,22 @@ package world.phantasmal.psoserv.servers.login +import mu.KLogger import world.phantasmal.psoserv.messages.BbAuthenticationStatus import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.servers.FinalServerState -import world.phantasmal.psoserv.servers.Server import world.phantasmal.psoserv.servers.ServerState +import world.phantasmal.psoserv.servers.ServerStateContext +import world.phantasmal.psoserv.servers.SocketSender class LoginContext( - private val sender: Server.ClientSender, + logger: KLogger, + socketSender: SocketSender, val characterServerAddress: ByteArray, val characterServerPort: Int, -) { - fun send(message: BbMessage, encrypt: Boolean = true) { - sender.send(message, encrypt) - } -} +) : ServerStateContext(logger, socketSender) -sealed class LoginState : ServerState() { - class Authentication(private val ctx: LoginContext) : LoginState() { +sealed class LoginState(ctx: LoginContext) : ServerState(ctx) { + class Authentication(ctx: LoginContext) : LoginState(ctx) { override fun process(message: BbMessage): LoginState = if (message is BbMessage.Authenticate) { // TODO: Actual authentication. @@ -35,13 +34,13 @@ sealed class LoginState : ServerState() { ) ) - Final + Final(ctx) } else { unexpectedMessage(message) } } - object Final : LoginState(), FinalServerState { + class Final(ctx: LoginContext) : LoginState(ctx), FinalServerState { override fun process(message: BbMessage): LoginState = unexpectedMessage(message) } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt index 52de6965..7d6eff52 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt @@ -1,37 +1,39 @@ package world.phantasmal.psoserv.servers.patch -import mu.KotlinLogging -import world.phantasmal.psolib.buffer.Buffer +import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.encryption.PcCipher -import world.phantasmal.psoserv.messages.Header import world.phantasmal.psoserv.messages.PcMessage import world.phantasmal.psoserv.messages.PcMessageDescriptor -import world.phantasmal.psoserv.servers.Server -import java.net.InetAddress +import world.phantasmal.psoserv.servers.GameServer +import world.phantasmal.psoserv.servers.Inet4Pair +import world.phantasmal.psoserv.servers.SocketSender -class PatchServer(address: InetAddress, port: Int, private val welcomeMessage: String) : - Server(KotlinLogging.logger {}, address, port) { +class PatchServer( + name: String, + bindPair: Inet4Pair, + private val welcomeMessage: String, +) : GameServer(name, bindPair) { + + override val messageDescriptor = PcMessageDescriptor override fun createCipher() = PcCipher() - override fun initializeState(sender: ClientSender): PatchState { - val ctx = PatchContext(sender, welcomeMessage) + override fun initializeState( + sender: SocketSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): PatchState { + val ctx = PatchContext(logger, sender, welcomeMessage) ctx.send( PcMessage.InitEncryption( "Patch Server. Copyright SonicTeam, LTD. 2001", - sender.serverCipher.key, - sender.clientCipher.key, + serverCipher.key, + clientCipher.key, ), encrypt = false, ) return PatchState.Welcome(ctx) } - - override fun readHeader(buffer: Buffer): Header = - PcMessageDescriptor.readHeader(buffer) - - override fun readMessage(buffer: Buffer): PcMessage = - PcMessageDescriptor.readMessage(buffer) } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt index 42c0f936..68a33320 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt @@ -1,21 +1,20 @@ package world.phantasmal.psoserv.servers.patch +import mu.KLogger import world.phantasmal.psoserv.messages.PcMessage import world.phantasmal.psoserv.servers.FinalServerState -import world.phantasmal.psoserv.servers.Server import world.phantasmal.psoserv.servers.ServerState +import world.phantasmal.psoserv.servers.ServerStateContext +import world.phantasmal.psoserv.servers.SocketSender class PatchContext( - private val sender: Server.ClientSender, + logger: KLogger, + socketSender: SocketSender, val welcomeMessage: String, -) { - fun send(message: PcMessage, encrypt: Boolean = true) { - sender.send(message, encrypt) - } -} +) : ServerStateContext(logger, socketSender) -sealed class PatchState : ServerState() { - class Welcome(private val ctx: PatchContext) : PatchState() { +sealed class PatchState(ctx: PatchContext) : ServerState(ctx) { + class Welcome(ctx: PatchContext) : PatchState(ctx) { override fun process(message: PcMessage): PatchState = if (message is PcMessage.InitEncryption) { ctx.send(PcMessage.Login()) @@ -26,7 +25,7 @@ sealed class PatchState : ServerState() { } } - class Login(private val ctx: PatchContext) : PatchState() { + class Login(ctx: PatchContext) : PatchState(ctx) { override fun process(message: PcMessage): PatchState = if (message is PcMessage.Login) { ctx.send(PcMessage.WelcomeMessage(ctx.welcomeMessage)) @@ -39,18 +38,18 @@ sealed class PatchState : ServerState() { } } - class PatchListDone(private val ctx: PatchContext) : PatchState() { + class PatchListDone(ctx: PatchContext) : PatchState(ctx) { override fun process(message: PcMessage): PatchState = if (message is PcMessage.PatchListOk) { ctx.send(PcMessage.PatchDone()) - Final + Final(ctx) } else { unexpectedMessage(message) } } - object Final : PatchState(), FinalServerState { + class Final(ctx: PatchContext) : PatchState(ctx), FinalServerState { override fun process(message: PcMessage): PatchState = unexpectedMessage(message) }