mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
psoserv game servers now use the same infrastructure as the proxy server.
This commit is contained in:
parent
d9f6869dd0
commit
a2285a5a03
@ -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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
}
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user