psoserv game servers now use the same infrastructure as the proxy server.

This commit is contained in:
Daan Vanden Bosch 2021-08-01 14:04:24 +02:00
parent d9f6869dd0
commit a2285a5a03
14 changed files with 295 additions and 368 deletions

View File

@ -70,16 +70,16 @@ fun main(args: Array<String>) {
} }
private class PhantasmalServer( private class PhantasmalServer(
private val servers: List<Server<*, *>>, private val servers: List<Server>,
private val proxyServers: List<ProxyServer>, private val proxyServers: List<ProxyServer>,
) { ) {
fun start() { fun start() {
servers.forEach(Server<*, *>::start) servers.forEach(Server::start)
proxyServers.forEach(ProxyServer::start) proxyServers.forEach(ProxyServer::start)
} }
fun stop() { fun stop() {
servers.forEach(Server<*, *>::stop) servers.forEach(Server::stop)
proxyServers.forEach(ProxyServer::stop) proxyServers.forEach(ProxyServer::stop)
} }
} }
@ -90,36 +90,40 @@ private fun initialize(config: Config): PhantasmalServer {
val dataAddress = config.data?.address?.let(::inet4Address) ?: defaultAddress val dataAddress = config.data?.address?.let(::inet4Address) ?: defaultAddress
val dataPort = config.data?.port ?: DEFAULT_DATA_PORT val dataPort = config.data?.port ?: DEFAULT_DATA_PORT
val servers = mutableListOf<Server<*, *>>() val servers = mutableListOf<Server>()
// If no proxy config is specified, we run a regular PSO server by default. // If no proxy config is specified, we run a regular PSO server by default.
val run = config.proxy == null || !config.proxy.run val run = config.proxy == null || !config.proxy.run
if (config.patch == null && run || config.patch?.run == true) { if (config.patch == null && run || config.patch?.run == true) {
val address = config.patch?.address?.let(::inet4Address) ?: defaultAddress val bindPair = Inet4Pair(
val port = config.patch?.port ?: DEFAULT_PATCH_PORT 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( servers.add(
PatchServer( PatchServer(
address, name = "patch",
port, bindPair,
welcomeMessage = config.patch?.welcomeMessage ?: "Welcome to Phantasmal World.", welcomeMessage = config.patch?.welcomeMessage ?: "Welcome to Phantasmal World.",
) )
) )
} }
if (config.login == null && run || config.login?.run == true) { if (config.login == null && run || config.login?.run == true) {
val address = config.login?.address?.let(::inet4Address) ?: defaultAddress val bindPair = Inet4Pair(
val port = config.login?.port ?: DEFAULT_LOGIN_PORT 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( servers.add(
LoginServer( LoginServer(
address, name = "login",
port, bindPair,
dataServerAddress = dataAddress, dataServerAddress = dataAddress,
dataServerPort = dataPort, dataServerPort = dataPort,
) )
@ -127,15 +131,17 @@ private fun initialize(config: Config): PhantasmalServer {
} }
if (config.data == null && run || config.data?.run == true) { if (config.data == null && run || config.data?.run == true) {
val address = config.data?.address?.let(::inet4Address) ?: defaultAddress val bindPair = Inet4Pair(
val port = config.data?.port ?: DEFAULT_DATA_PORT 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( servers.add(
DataServer( DataServer(
address, name = "data",
port, bindPair,
) )
) )
} }

View File

@ -1,24 +1,15 @@
package world.phantasmal.psoserv.servers package world.phantasmal.psoserv.servers
import mu.KLogger
import world.phantasmal.psolib.buffer.Buffer
import world.phantasmal.psoserv.encryption.BbCipher import world.phantasmal.psoserv.encryption.BbCipher
import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.messages.BbMessage
import world.phantasmal.psoserv.messages.BbMessageDescriptor import world.phantasmal.psoserv.messages.BbMessageDescriptor
import world.phantasmal.psoserv.messages.Header
import java.net.InetAddress
abstract class BbServer<StateType : ServerState<BbMessage, StateType>>( abstract class BbServer<StateType : ServerState<BbMessage, *, StateType>>(
logger: KLogger, name: String,
address: InetAddress, bindPair: Inet4Pair,
port: Int, ) : GameServer<BbMessage, StateType>(name, bindPair) {
) : Server<BbMessage, StateType>(logger, address, port) {
override val messageDescriptor = BbMessageDescriptor
override fun createCipher() = BbCipher() override fun createCipher() = BbCipher()
override fun readHeader(buffer: Buffer): Header =
BbMessageDescriptor.readHeader(buffer)
override fun readMessage(buffer: Buffer): BbMessage =
BbMessageDescriptor.readMessage(buffer)
} }

View File

@ -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<MessageType, StateType>(
name: String,
bindPair: Inet4Pair,
) : Server(name, bindPair)
where MessageType : Message,
StateType : ServerState<MessageType, *, StateType> {
private var connectionCounter = 0
protected abstract val messageDescriptor: MessageDescriptor<MessageType>
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<MessageType>,
serverCipher: Cipher,
clientCipher: Cipher,
): StateType
private inner class ClientHandler(socket: Socket) : SocketHandler<MessageType>(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
}
}
}
}

View File

@ -1,66 +1,23 @@
package world.phantasmal.psoserv.servers package world.phantasmal.psoserv.servers
import mu.KotlinLogging
import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psolib.buffer.Buffer
import world.phantasmal.psoserv.encryption.Cipher import world.phantasmal.psoserv.encryption.Cipher
import world.phantasmal.psoserv.messages.InitEncryptionMessage import world.phantasmal.psoserv.messages.InitEncryptionMessage
import world.phantasmal.psoserv.messages.Message import world.phantasmal.psoserv.messages.Message
import world.phantasmal.psoserv.messages.MessageDescriptor import world.phantasmal.psoserv.messages.MessageDescriptor
import world.phantasmal.psoserv.messages.RedirectMessage import world.phantasmal.psoserv.messages.RedirectMessage
import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import java.net.SocketException
import java.net.SocketTimeoutException
class ProxyServer( class ProxyServer(
private val name: String, name: String,
private val bindPair: Inet4Pair, bindPair: Inet4Pair,
private val remotePair: Inet4Pair, private val remotePair: Inet4Pair,
private val messageDescriptor: MessageDescriptor<Message>, private val messageDescriptor: MessageDescriptor<Message>,
private val createCipher: (key: ByteArray) -> Cipher, private val createCipher: (key: ByteArray) -> Cipher,
private val redirectMap: Map<Inet4Pair, Inet4Pair> = emptyMap(), private val redirectMap: Map<Inet4Pair, Inet4Pair> = emptyMap(),
) { ) : Server(name, bindPair) {
private val logger = KotlinLogging.logger(name)
private val proxySocket = ServerSocket()
@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}."
}
override fun clientConnected(clientSocket: Socket) {
val serverSocket = Socket(remotePair.address, remotePair.port) val serverSocket = Socket(remotePair.address, remotePair.port)
logger.info { logger.info {
"Connected to server ${serverSocket.inetAddress}:${serverSocket.port}." "Connected to server ${serverSocket.inetAddress}:${serverSocket.port}."
@ -69,32 +26,6 @@ class ProxyServer(
// Listen to server on this thread. // Listen to server on this thread.
// Don't start listening to the client until encryption is initialized. // Don't start listening to the client until encryption is initialized.
ServerHandler(serverSocket, clientSocket).listen() 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."
}
}
}
}
logger.info { "Stopped." }
} }
private inner class ServerHandler( private inner class ServerHandler(
@ -109,14 +40,15 @@ class ProxyServer(
// The first message sent by the server is always unencrypted and initializes the // 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 // encryption. We don't start listening to the client until the encryption is
// initialized. // initialized.
override var decryptCipher: Cipher? = null override var readDecryptCipher: Cipher? = null
override var encryptCipher: Cipher? = null override var readEncryptCipher: Cipher? = null
override val writeEncryptCipher: Cipher? = null
override fun processMessage(message: Message): ProcessResult { override fun processMessage(message: Message): ProcessResult {
when (message) { when (message) {
is InitEncryptionMessage -> if (decryptCipher == null) { is InitEncryptionMessage -> if (readDecryptCipher == null) {
decryptCipher = createCipher(message.serverKey) readDecryptCipher = createCipher(message.serverKey)
encryptCipher = createCipher(message.serverKey) readEncryptCipher = createCipher(message.serverKey)
val clientDecryptCipher = createCipher(message.clientKey) val clientDecryptCipher = createCipher(message.clientKey)
val clientEncryptCipher = createCipher(message.clientKey) val clientEncryptCipher = createCipher(message.clientKey)
@ -170,11 +102,12 @@ class ProxyServer(
private inner class ClientHandler( private inner class ClientHandler(
clientSocket: Socket, clientSocket: Socket,
private val serverHandler: ServerHandler, private val serverHandler: ServerHandler,
override val decryptCipher: Cipher, override val readDecryptCipher: Cipher,
override val encryptCipher: Cipher, override val readEncryptCipher: Cipher,
) : SocketHandler<Message>(logger, clientSocket) { ) : SocketHandler<Message>(logger, clientSocket) {
override val messageDescriptor = this@ProxyServer.messageDescriptor override val messageDescriptor = this@ProxyServer.messageDescriptor
override val writeEncryptCipher: Cipher? = null
override fun processMessage(message: Message): ProcessResult = ProcessResult.Ok override fun processMessage(message: Message): ProcessResult = ProcessResult.Ok

View File

@ -1,34 +1,32 @@
package world.phantasmal.psoserv.servers package world.phantasmal.psoserv.servers
import mu.KLogger import mu.KotlinLogging
import world.phantasmal.psolib.Endianness import java.net.ServerSocket
import world.phantasmal.psolib.buffer.Buffer import java.net.Socket
import world.phantasmal.psoserv.encryption.Cipher import java.net.SocketException
import world.phantasmal.psoserv.messages.Header import java.net.SocketTimeoutException
import world.phantasmal.psoserv.messages.Message
import world.phantasmal.psoserv.messages.messageString
import world.phantasmal.psoserv.roundToBlockSize
import java.net.*
private const val MAX_MSG_SIZE: Int = 32768 abstract class Server(
protected val name: String,
abstract class Server<MessageType : Message, StateType : ServerState<MessageType, StateType>>( private val bindPair: Inet4Pair,
private val logger: KLogger,
private val address: InetAddress,
private val port: Int,
) { ) {
private val serverSocket = ServerSocket(port, 50, address) private val bindSocket = ServerSocket()
private var connectionCounter = 0
@Volatile @Volatile
private var running = true private var running = false
protected val logger = KotlinLogging.logger(name)
fun start() { fun start() {
logger.info { "Starting." } logger.info { "Starting." }
bindSocket.bind(bindPair)
running = true
// Accept client connections on a dedicated thread. // Accept client connections on a dedicated thread.
val thread = Thread(::acceptConnections) val thread = Thread(::acceptConnections)
thread.name = this::class.simpleName thread.name = name
thread.start() thread.start()
} }
@ -40,7 +38,8 @@ abstract class Server<MessageType : Message, StateType : ServerState<MessageType
// Closing the server socket will generate a SocketException on the connection thread which // Closing the server socket will generate a SocketException on the connection thread which
// will then shut down. // will then shut down.
serverSocket.close() // TODO: Shut down client threads when server is stopped.
bindSocket.close()
} }
private fun acceptConnections() { private fun acceptConnections() {
@ -49,19 +48,18 @@ abstract class Server<MessageType : Message, StateType : ServerState<MessageType
while (running) { while (running) {
try { try {
val socket = serverSocket.accept() val clientSocket = bindSocket.accept()
logger.info { "New client connection from ${socket.inetAddress}." } logger.info {
// Handle each client connection in its own thread. "New client connection from ${clientSocket.inetAddress}:${clientSocket.port}."
// TODO: Shut down client threads when server is stopped. }
val thread = Thread { clientConnected(socket) }
thread.name = "${this::class.simpleName} client ${connectionCounter++}" clientConnected(clientSocket)
thread.start()
} catch (e: SocketTimeoutException) { } catch (e: SocketTimeoutException) {
// Retry after timeout. // Retry after timeout.
continue continue
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
logger.error(e) { logger.error(e) {
"Interrupted while trying to accept client connections on $address:$port, stopping." "Interrupted while trying to accept client connections on $bindPair, stopping."
} }
break break
} catch (e: SocketException) { } catch (e: SocketException) {
@ -69,13 +67,13 @@ abstract class Server<MessageType : Message, StateType : ServerState<MessageType
// probably generated by a socket.close() call. // probably generated by a socket.close() call.
if (running) { if (running) {
logger.error(e) { logger.error(e) {
"Exception while trying to accept client connections on $address:$port, stopping." "Exception while trying to accept client connections on $bindPair, stopping."
} }
} }
break break
} catch (e: Throwable) { } catch (e: Throwable) {
logger.error(e) { logger.error(e) {
"Exception while trying to accept client connections on $address:$port." "Exception while trying to accept client connections on $bindPair."
} }
} }
} }
@ -84,139 +82,5 @@ abstract class Server<MessageType : Message, StateType : ServerState<MessageType
logger.info { "Stopped." } logger.info { "Stopped." }
} }
protected abstract fun clientConnected(clientSocket: Socket)
private fun clientConnected(socket: Socket) {
try {
val readBuffer = Buffer.withSize(MAX_MSG_SIZE, Endianness.Little)
var read = 0
var maxRead = MAX_MSG_SIZE
var headerDecrypted = false
val serverCipher = createCipher()
val clientCipher = createCipher()
val sender = ClientSender(logger, socket, serverCipher, clientCipher)
var state: StateType = initializeState(sender)
while (true) {
val readNow = socket.getInputStream().read(readBuffer.byteArray, read, maxRead)
if (readNow == -1) {
// Close the connection.
break
}
read += readNow
maxRead = MAX_MSG_SIZE - read
if (read >= 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)
}
}
} }

View File

@ -1,19 +1,31 @@
package world.phantasmal.psoserv.servers package world.phantasmal.psoserv.servers
import mu.KotlinLogging import mu.KLogger
import world.phantasmal.psoserv.messages.Message import world.phantasmal.psoserv.messages.Message
abstract class ServerState<ClientMsgType : Message, Self : ServerState<ClientMsgType, Self>> { abstract class ServerStateContext<MessageType : Message>(
private val logger = KotlinLogging.logger(this::class.qualifiedName!!) val logger: KLogger,
private val socketSender: SocketSender<MessageType>,
) {
fun send(message: MessageType, encrypt: Boolean = true) {
socketSender.sendMessage(message, encrypt)
}
}
abstract class ServerState<MessageType, ContextType, Self>(
protected val ctx: ContextType,
) where MessageType : Message,
ContextType : ServerStateContext<MessageType>,
Self : ServerState<MessageType, ContextType, Self> {
init { 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 { protected fun unexpectedMessage(message: MessageType): Self {
logger.debug { "Unexpected message: $message." } ctx.logger.debug { "Unexpected message: $message." }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return this as Self return this as Self
} }

View File

@ -15,7 +15,7 @@ import kotlin.math.min
abstract class SocketHandler<MessageType : Message>( abstract class SocketHandler<MessageType : Message>(
protected val logger: KLogger, protected val logger: KLogger,
private val socket: Socket, private val socket: Socket,
) { ) : SocketSender<MessageType> {
private val sockName: String = "${socket.remoteSocketAddress}" private val sockName: String = "${socket.remoteSocketAddress}"
private val headerSize: Int get() = messageDescriptor.headerSize private val headerSize: Int get() = messageDescriptor.headerSize
@ -23,8 +23,14 @@ abstract class SocketHandler<MessageType : Message>(
private var running = false private var running = false
protected abstract val messageDescriptor: MessageDescriptor<MessageType> protected abstract val messageDescriptor: MessageDescriptor<MessageType>
protected abstract val decryptCipher: Cipher? protected abstract val readDecryptCipher: Cipher?
protected abstract val encryptCipher: 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() { fun listen() {
logger.info { "Listening to $sockName." } logger.info { "Listening to $sockName." }
@ -50,8 +56,8 @@ abstract class SocketHandler<MessageType : Message>(
bufferLoop@ while (offset + headerSize <= readBuffer.size) { bufferLoop@ while (offset + headerSize <= readBuffer.size) {
// Remember the current cipher in a local variable because processing a message // Remember the current cipher in a local variable because processing a message
// might change it. // might change it.
val decryptCipher = decryptCipher val decryptCipher = readDecryptCipher
val encryptCipher = encryptCipher val encryptCipher = readEncryptCipher
// Read header. // Read header.
readBuffer.copyInto(headerBuffer, offset = offset, size = headerSize) readBuffer.copyInto(headerBuffer, offset = offset, size = headerSize)
@ -217,6 +223,35 @@ abstract class SocketHandler<MessageType : Message>(
socket.close() 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 abstract fun processMessage(message: MessageType): ProcessResult
protected open fun processRawBytes(buffer: Buffer, offset: Int, size: Int) { protected open fun processRawBytes(buffer: Buffer, offset: Int, size: Int) {

View File

@ -0,0 +1,7 @@
package world.phantasmal.psoserv.servers
import world.phantasmal.psoserv.messages.Message
interface SocketSender<MessageType : Message> {
fun sendMessage(message: MessageType, encrypt: Boolean = true)
}

View File

@ -1,21 +1,28 @@
package world.phantasmal.psoserv.servers.character 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.messages.BbMessage
import world.phantasmal.psoserv.servers.BbServer 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) : class DataServer(
BbServer<DataState>(KotlinLogging.logger {}, address, port) { name: String,
bindPair: Inet4Pair,
) : BbServer<DataState>(name, bindPair) {
override fun initializeState(sender: ClientSender): DataState { override fun initializeState(
val ctx = DataContext(sender) sender: SocketSender<BbMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): DataState {
val ctx = DataContext(logger, sender)
ctx.send( ctx.send(
BbMessage.InitEncryption( BbMessage.InitEncryption(
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
sender.serverCipher.key, serverCipher.key,
sender.clientCipher.key, clientCipher.key,
), ),
encrypt = false, encrypt = false,
) )

View File

@ -1,5 +1,6 @@
package world.phantasmal.psoserv.servers.character package world.phantasmal.psoserv.servers.character
import mu.KLogger
import world.phantasmal.core.math.clamp import world.phantasmal.core.math.clamp
import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psolib.buffer.Buffer
import world.phantasmal.psolib.cursor.cursor 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.BbMessage
import world.phantasmal.psoserv.messages.PsoCharacter import world.phantasmal.psoserv.messages.PsoCharacter
import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.FinalServerState
import world.phantasmal.psoserv.servers.Server
import world.phantasmal.psoserv.servers.ServerState import world.phantasmal.psoserv.servers.ServerState
import world.phantasmal.psoserv.servers.ServerStateContext
import world.phantasmal.psoserv.servers.SocketSender
import kotlin.math.min import kotlin.math.min
class DataContext( class DataContext(
private val sender: Server.ClientSender, logger: KLogger,
) { socketSender: SocketSender<BbMessage>,
fun send(message: BbMessage, encrypt: Boolean = true) { ) : ServerStateContext<BbMessage>(logger, socketSender)
sender.send(message, encrypt)
}
}
sealed class DataState : ServerState<BbMessage, DataState>() { sealed class DataState(ctx: DataContext) : ServerState<BbMessage, DataContext, DataState>(ctx) {
class Authentication(private val ctx: DataContext) : DataState() { class Authentication(ctx: DataContext) : DataState(ctx) {
override fun process(message: BbMessage): DataState = override fun process(message: BbMessage): DataState =
if (message is BbMessage.Authenticate) { if (message is BbMessage.Authenticate) {
// TODO: Actual authentication. // TODO: Actual authentication.
@ -38,7 +37,7 @@ sealed class DataState : ServerState<BbMessage, DataState>() {
} }
} }
class Account(private val ctx: DataContext) : DataState() { class Account(ctx: DataContext) : DataState(ctx) {
override fun process(message: BbMessage): DataState = override fun process(message: BbMessage): DataState =
if (message is BbMessage.GetAccount) { if (message is BbMessage.GetAccount) {
// TODO: Send correct guild card number and team ID. // TODO: Send correct guild card number and team ID.
@ -50,7 +49,7 @@ sealed class DataState : ServerState<BbMessage, DataState>() {
} }
} }
class CharacterSelect(private val ctx: DataContext) : DataState() { class CharacterSelect(ctx: DataContext) : DataState(ctx) {
override fun process(message: BbMessage): DataState = override fun process(message: BbMessage): DataState =
when (message) { when (message) {
is BbMessage.CharacterSelect -> { is BbMessage.CharacterSelect -> {
@ -97,7 +96,7 @@ sealed class DataState : ServerState<BbMessage, DataState>() {
} }
} }
class DataDownload(private val ctx: DataContext) : DataState() { class DataDownload(ctx: DataContext) : DataState(ctx) {
private val guildCardBuffer = Buffer.withSize(54672) private val guildCardBuffer = Buffer.withSize(54672)
private val fileBuffer = Buffer.withSize(0) private val fileBuffer = Buffer.withSize(0)
private var fileChunkNo = 0 private var fileChunkNo = 0
@ -178,7 +177,7 @@ sealed class DataState : ServerState<BbMessage, DataState>() {
} }
} }
object Final : DataState(), FinalServerState { class Final(ctx: DataContext) : DataState(ctx), FinalServerState {
override fun process(message: BbMessage): DataState = override fun process(message: BbMessage): DataState =
unexpectedMessage(message) unexpectedMessage(message)
} }

View File

@ -1,26 +1,31 @@
package world.phantasmal.psoserv.servers.login 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.messages.BbMessage
import world.phantasmal.psoserv.servers.BbServer 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.Inet4Address
import java.net.InetAddress
class LoginServer( class LoginServer(
address: InetAddress, name: String,
port: Int, bindPair: Inet4Pair,
private val dataServerAddress: Inet4Address, private val dataServerAddress: Inet4Address,
private val dataServerPort: Int, private val dataServerPort: Int,
) : BbServer<LoginState>(KotlinLogging.logger {}, address, port) { ) : BbServer<LoginState>(name, bindPair) {
override fun initializeState(sender: ClientSender): LoginState { override fun initializeState(
val ctx = LoginContext(sender, dataServerAddress.address, dataServerPort) sender: SocketSender<BbMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): LoginState {
val ctx = LoginContext(logger, sender, dataServerAddress.address, dataServerPort)
ctx.send( ctx.send(
BbMessage.InitEncryption( BbMessage.InitEncryption(
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.", "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
sender.serverCipher.key, serverCipher.key,
sender.clientCipher.key, clientCipher.key,
), ),
encrypt = false, encrypt = false,
) )

View File

@ -1,23 +1,22 @@
package world.phantasmal.psoserv.servers.login package world.phantasmal.psoserv.servers.login
import mu.KLogger
import world.phantasmal.psoserv.messages.BbAuthenticationStatus import world.phantasmal.psoserv.messages.BbAuthenticationStatus
import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.messages.BbMessage
import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.FinalServerState
import world.phantasmal.psoserv.servers.Server
import world.phantasmal.psoserv.servers.ServerState import world.phantasmal.psoserv.servers.ServerState
import world.phantasmal.psoserv.servers.ServerStateContext
import world.phantasmal.psoserv.servers.SocketSender
class LoginContext( class LoginContext(
private val sender: Server.ClientSender, logger: KLogger,
socketSender: SocketSender<BbMessage>,
val characterServerAddress: ByteArray, val characterServerAddress: ByteArray,
val characterServerPort: Int, val characterServerPort: Int,
) { ) : ServerStateContext<BbMessage>(logger, socketSender)
fun send(message: BbMessage, encrypt: Boolean = true) {
sender.send(message, encrypt)
}
}
sealed class LoginState : ServerState<BbMessage, LoginState>() { sealed class LoginState(ctx: LoginContext) : ServerState<BbMessage, LoginContext, LoginState>(ctx) {
class Authentication(private val ctx: LoginContext) : LoginState() { class Authentication(ctx: LoginContext) : LoginState(ctx) {
override fun process(message: BbMessage): LoginState = override fun process(message: BbMessage): LoginState =
if (message is BbMessage.Authenticate) { if (message is BbMessage.Authenticate) {
// TODO: Actual authentication. // TODO: Actual authentication.
@ -35,13 +34,13 @@ sealed class LoginState : ServerState<BbMessage, LoginState>() {
) )
) )
Final Final(ctx)
} else { } else {
unexpectedMessage(message) unexpectedMessage(message)
} }
} }
object Final : LoginState(), FinalServerState { class Final(ctx: LoginContext) : LoginState(ctx), FinalServerState {
override fun process(message: BbMessage): LoginState = override fun process(message: BbMessage): LoginState =
unexpectedMessage(message) unexpectedMessage(message)
} }

View File

@ -1,37 +1,39 @@
package world.phantasmal.psoserv.servers.patch package world.phantasmal.psoserv.servers.patch
import mu.KotlinLogging import world.phantasmal.psoserv.encryption.Cipher
import world.phantasmal.psolib.buffer.Buffer
import world.phantasmal.psoserv.encryption.PcCipher import world.phantasmal.psoserv.encryption.PcCipher
import world.phantasmal.psoserv.messages.Header
import world.phantasmal.psoserv.messages.PcMessage import world.phantasmal.psoserv.messages.PcMessage
import world.phantasmal.psoserv.messages.PcMessageDescriptor import world.phantasmal.psoserv.messages.PcMessageDescriptor
import world.phantasmal.psoserv.servers.Server import world.phantasmal.psoserv.servers.GameServer
import java.net.InetAddress import world.phantasmal.psoserv.servers.Inet4Pair
import world.phantasmal.psoserv.servers.SocketSender
class PatchServer(address: InetAddress, port: Int, private val welcomeMessage: String) : class PatchServer(
Server<PcMessage, PatchState>(KotlinLogging.logger {}, address, port) { name: String,
bindPair: Inet4Pair,
private val welcomeMessage: String,
) : GameServer<PcMessage, PatchState>(name, bindPair) {
override val messageDescriptor = PcMessageDescriptor
override fun createCipher() = PcCipher() override fun createCipher() = PcCipher()
override fun initializeState(sender: ClientSender): PatchState { override fun initializeState(
val ctx = PatchContext(sender, welcomeMessage) sender: SocketSender<PcMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): PatchState {
val ctx = PatchContext(logger, sender, welcomeMessage)
ctx.send( ctx.send(
PcMessage.InitEncryption( PcMessage.InitEncryption(
"Patch Server. Copyright SonicTeam, LTD. 2001", "Patch Server. Copyright SonicTeam, LTD. 2001",
sender.serverCipher.key, serverCipher.key,
sender.clientCipher.key, clientCipher.key,
), ),
encrypt = false, encrypt = false,
) )
return PatchState.Welcome(ctx) return PatchState.Welcome(ctx)
} }
override fun readHeader(buffer: Buffer): Header =
PcMessageDescriptor.readHeader(buffer)
override fun readMessage(buffer: Buffer): PcMessage =
PcMessageDescriptor.readMessage(buffer)
} }

View File

@ -1,21 +1,20 @@
package world.phantasmal.psoserv.servers.patch package world.phantasmal.psoserv.servers.patch
import mu.KLogger
import world.phantasmal.psoserv.messages.PcMessage import world.phantasmal.psoserv.messages.PcMessage
import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.FinalServerState
import world.phantasmal.psoserv.servers.Server
import world.phantasmal.psoserv.servers.ServerState import world.phantasmal.psoserv.servers.ServerState
import world.phantasmal.psoserv.servers.ServerStateContext
import world.phantasmal.psoserv.servers.SocketSender
class PatchContext( class PatchContext(
private val sender: Server.ClientSender, logger: KLogger,
socketSender: SocketSender<PcMessage>,
val welcomeMessage: String, val welcomeMessage: String,
) { ) : ServerStateContext<PcMessage>(logger, socketSender)
fun send(message: PcMessage, encrypt: Boolean = true) {
sender.send(message, encrypt)
}
}
sealed class PatchState : ServerState<PcMessage, PatchState>() { sealed class PatchState(ctx: PatchContext) : ServerState<PcMessage, PatchContext, PatchState>(ctx) {
class Welcome(private val ctx: PatchContext) : PatchState() { class Welcome(ctx: PatchContext) : PatchState(ctx) {
override fun process(message: PcMessage): PatchState = override fun process(message: PcMessage): PatchState =
if (message is PcMessage.InitEncryption) { if (message is PcMessage.InitEncryption) {
ctx.send(PcMessage.Login()) ctx.send(PcMessage.Login())
@ -26,7 +25,7 @@ sealed class PatchState : ServerState<PcMessage, PatchState>() {
} }
} }
class Login(private val ctx: PatchContext) : PatchState() { class Login(ctx: PatchContext) : PatchState(ctx) {
override fun process(message: PcMessage): PatchState = override fun process(message: PcMessage): PatchState =
if (message is PcMessage.Login) { if (message is PcMessage.Login) {
ctx.send(PcMessage.WelcomeMessage(ctx.welcomeMessage)) ctx.send(PcMessage.WelcomeMessage(ctx.welcomeMessage))
@ -39,18 +38,18 @@ sealed class PatchState : ServerState<PcMessage, PatchState>() {
} }
} }
class PatchListDone(private val ctx: PatchContext) : PatchState() { class PatchListDone(ctx: PatchContext) : PatchState(ctx) {
override fun process(message: PcMessage): PatchState = override fun process(message: PcMessage): PatchState =
if (message is PcMessage.PatchListOk) { if (message is PcMessage.PatchListOk) {
ctx.send(PcMessage.PatchDone()) ctx.send(PcMessage.PatchDone())
Final Final(ctx)
} else { } else {
unexpectedMessage(message) unexpectedMessage(message)
} }
} }
object Final : PatchState(), FinalServerState { class Final(ctx: PatchContext) : PatchState(ctx), FinalServerState {
override fun process(message: PcMessage): PatchState = override fun process(message: PcMessage): PatchState =
unexpectedMessage(message) unexpectedMessage(message)
} }