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 val servers: List<Server<*, *>>,
private val servers: List<Server>,
private val proxyServers: List<ProxyServer>,
) {
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<Server<*, *>>()
val servers = mutableListOf<Server>()
// 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,
)
)
}

View File

@ -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<StateType : ServerState<BbMessage, StateType>>(
logger: KLogger,
address: InetAddress,
port: Int,
) : Server<BbMessage, StateType>(logger, address, port) {
abstract class BbServer<StateType : ServerState<BbMessage, *, StateType>>(
name: String,
bindPair: Inet4Pair,
) : GameServer<BbMessage, StateType>(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)
}

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
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<Message>,
private val createCipher: (key: ByteArray) -> Cipher,
private val redirectMap: Map<Inet4Pair, Inet4Pair> = emptyMap(),
) {
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}."
}
) : Server(name, bindPair) {
override fun clientConnected(clientSocket: Socket) {
val serverSocket = Socket(remotePair.address, remotePair.port)
logger.info {
"Connected to server ${serverSocket.inetAddress}:${serverSocket.port}."
@ -69,32 +26,6 @@ class ProxyServer(
// 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."
}
}
}
}
logger.info { "Stopped." }
}
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<Message>(logger, clientSocket) {
override val messageDescriptor = this@ProxyServer.messageDescriptor
override val writeEncryptCipher: Cipher? = null
override fun processMessage(message: Message): ProcessResult = ProcessResult.Ok

View File

@ -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<MessageType : Message, StateType : ServerState<MessageType, StateType>>(
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<MessageType : Message, StateType : ServerState<MessageType
// Closing the server socket will generate a SocketException on the connection thread which
// will then shut down.
serverSocket.close()
// TODO: Shut down client threads when server is stopped.
bindSocket.close()
}
private fun acceptConnections() {
@ -49,19 +48,18 @@ abstract class Server<MessageType : Message, StateType : ServerState<MessageType
while (running) {
try {
val socket = serverSocket.accept()
logger.info { "New client connection from ${socket.inetAddress}." }
// Handle each client connection in its own thread.
// TODO: Shut down client threads when server is stopped.
val thread = Thread { clientConnected(socket) }
thread.name = "${this::class.simpleName} client ${connectionCounter++}"
thread.start()
val clientSocket = bindSocket.accept()
logger.info {
"New client connection from ${clientSocket.inetAddress}:${clientSocket.port}."
}
clientConnected(clientSocket)
} catch (e: SocketTimeoutException) {
// Retry after timeout.
continue
} catch (e: InterruptedException) {
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
} catch (e: SocketException) {
@ -69,13 +67,13 @@ abstract class Server<MessageType : Message, StateType : ServerState<MessageType
// probably generated by a socket.close() call.
if (running) {
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
} catch (e: Throwable) {
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." }
}
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)
}
}
protected abstract fun clientConnected(clientSocket: Socket)
}

View File

@ -1,19 +1,31 @@
package world.phantasmal.psoserv.servers
import mu.KotlinLogging
import mu.KLogger
import world.phantasmal.psoserv.messages.Message
abstract class ServerState<ClientMsgType : Message, Self : ServerState<ClientMsgType, Self>> {
private val logger = KotlinLogging.logger(this::class.qualifiedName!!)
abstract class ServerStateContext<MessageType : Message>(
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 {
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
}

View File

@ -15,7 +15,7 @@ import kotlin.math.min
abstract class SocketHandler<MessageType : Message>(
protected val logger: KLogger,
private val socket: Socket,
) {
) : SocketSender<MessageType> {
private val sockName: String = "${socket.remoteSocketAddress}"
private val headerSize: Int get() = messageDescriptor.headerSize
@ -23,8 +23,14 @@ abstract class SocketHandler<MessageType : Message>(
private var running = false
protected abstract val messageDescriptor: MessageDescriptor<MessageType>
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<MessageType : Message>(
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<MessageType : Message>(
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) {

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
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<DataState>(KotlinLogging.logger {}, address, port) {
class DataServer(
name: String,
bindPair: Inet4Pair,
) : BbServer<DataState>(name, bindPair) {
override fun initializeState(sender: ClientSender): DataState {
val ctx = DataContext(sender)
override fun initializeState(
sender: SocketSender<BbMessage>,
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,
)

View File

@ -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<BbMessage>,
) : ServerStateContext<BbMessage>(logger, socketSender)
sealed class DataState : ServerState<BbMessage, DataState>() {
class Authentication(private val ctx: DataContext) : DataState() {
sealed class DataState(ctx: DataContext) : ServerState<BbMessage, DataContext, DataState>(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<BbMessage, DataState>() {
}
}
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<BbMessage, DataState>() {
}
}
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<BbMessage, DataState>() {
}
}
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<BbMessage, DataState>() {
}
}
object Final : DataState(), FinalServerState {
class Final(ctx: DataContext) : DataState(ctx), FinalServerState {
override fun process(message: BbMessage): DataState =
unexpectedMessage(message)
}

View File

@ -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<LoginState>(KotlinLogging.logger {}, address, port) {
) : BbServer<LoginState>(name, bindPair) {
override fun initializeState(sender: ClientSender): LoginState {
val ctx = LoginContext(sender, dataServerAddress.address, dataServerPort)
override fun initializeState(
sender: SocketSender<BbMessage>,
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,
)

View File

@ -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<BbMessage>,
val characterServerAddress: ByteArray,
val characterServerPort: Int,
) {
fun send(message: BbMessage, encrypt: Boolean = true) {
sender.send(message, encrypt)
}
}
) : ServerStateContext<BbMessage>(logger, socketSender)
sealed class LoginState : ServerState<BbMessage, LoginState>() {
class Authentication(private val ctx: LoginContext) : LoginState() {
sealed class LoginState(ctx: LoginContext) : ServerState<BbMessage, LoginContext, LoginState>(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<BbMessage, LoginState>() {
)
)
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)
}

View File

@ -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<PcMessage, PatchState>(KotlinLogging.logger {}, address, port) {
class PatchServer(
name: String,
bindPair: Inet4Pair,
private val welcomeMessage: String,
) : GameServer<PcMessage, PatchState>(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<PcMessage>,
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)
}

View File

@ -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<PcMessage>,
val welcomeMessage: String,
) {
fun send(message: PcMessage, encrypt: Boolean = true) {
sender.send(message, encrypt)
}
}
) : ServerStateContext<PcMessage>(logger, socketSender)
sealed class PatchState : ServerState<PcMessage, PatchState>() {
class Welcome(private val ctx: PatchContext) : PatchState() {
sealed class PatchState(ctx: PatchContext) : ServerState<PcMessage, PatchContext, PatchState>(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<PcMessage, PatchState>() {
}
}
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<PcMessage, PatchState>() {
}
}
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)
}