mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 07:18:29 +08:00
Simplified psoserv architecture.
This commit is contained in:
parent
f6e0ed06a9
commit
810c1cb549
@ -11,9 +11,6 @@ import world.phantasmal.psoserv.messages.Message
|
|||||||
import world.phantasmal.psoserv.messages.MessageDescriptor
|
import world.phantasmal.psoserv.messages.MessageDescriptor
|
||||||
import world.phantasmal.psoserv.messages.PcMessageDescriptor
|
import world.phantasmal.psoserv.messages.PcMessageDescriptor
|
||||||
import world.phantasmal.psoserv.servers.*
|
import world.phantasmal.psoserv.servers.*
|
||||||
import world.phantasmal.psoserv.servers.account.AccountServer
|
|
||||||
import world.phantasmal.psoserv.servers.auth.AuthServer
|
|
||||||
import world.phantasmal.psoserv.servers.patch.PatchServer
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.Inet4Address
|
import java.net.Inet4Address
|
||||||
|
|
||||||
@ -21,7 +18,7 @@ import java.net.Inet4Address
|
|||||||
private val DEFAULT_ADDRESS: Inet4Address = inet4Loopback()
|
private val DEFAULT_ADDRESS: Inet4Address = inet4Loopback()
|
||||||
private const val DEFAULT_PATCH_PORT: Int = 11_000
|
private const val DEFAULT_PATCH_PORT: Int = 11_000
|
||||||
private const val DEFAULT_LOGIN_PORT: Int = 12_000
|
private const val DEFAULT_LOGIN_PORT: Int = 12_000
|
||||||
private const val DEFAULT_DATA_PORT: Int = 12_001
|
private const val DEFAULT_ACCOUNT_PORT: Int = 12_001
|
||||||
|
|
||||||
private val LOGGER = KotlinLogging.logger("main")
|
private val LOGGER = KotlinLogging.logger("main")
|
||||||
|
|
||||||
@ -87,8 +84,8 @@ private class PhantasmalServer(
|
|||||||
private fun initialize(config: Config): PhantasmalServer {
|
private fun initialize(config: Config): PhantasmalServer {
|
||||||
val defaultAddress = config.address?.let(::inet4Address) ?: DEFAULT_ADDRESS
|
val defaultAddress = config.address?.let(::inet4Address) ?: DEFAULT_ADDRESS
|
||||||
|
|
||||||
val dataAddress = config.account?.address?.let(::inet4Address) ?: defaultAddress
|
val accountAddress = config.account?.address?.let(::inet4Address) ?: defaultAddress
|
||||||
val dataPort = config.account?.port ?: DEFAULT_DATA_PORT
|
val accountPort = config.account?.port ?: DEFAULT_ACCOUNT_PORT
|
||||||
|
|
||||||
val servers = mutableListOf<Server>()
|
val servers = mutableListOf<Server>()
|
||||||
|
|
||||||
@ -124,8 +121,8 @@ private fun initialize(config: Config): PhantasmalServer {
|
|||||||
AuthServer(
|
AuthServer(
|
||||||
name = "auth",
|
name = "auth",
|
||||||
bindPair,
|
bindPair,
|
||||||
dataServerAddress = dataAddress,
|
accountServerAddress = accountAddress,
|
||||||
dataServerPort = dataPort,
|
accountServerPort = accountPort,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -133,7 +130,7 @@ private fun initialize(config: Config): PhantasmalServer {
|
|||||||
if (config.account == null && run || config.account?.run == true) {
|
if (config.account == null && run || config.account?.run == true) {
|
||||||
val bindPair = Inet4Pair(
|
val bindPair = Inet4Pair(
|
||||||
config.account?.address?.let(::inet4Address) ?: defaultAddress,
|
config.account?.address?.let(::inet4Address) ?: defaultAddress,
|
||||||
config.account?.port ?: DEFAULT_DATA_PORT,
|
config.account?.port ?: DEFAULT_ACCOUNT_PORT,
|
||||||
)
|
)
|
||||||
|
|
||||||
LOGGER.info { "Configuring account server to bind to $bindPair." }
|
LOGGER.info { "Configuring account server to bind to $bindPair." }
|
||||||
|
@ -0,0 +1,241 @@
|
|||||||
|
package world.phantasmal.psoserv.servers
|
||||||
|
|
||||||
|
import world.phantasmal.core.math.clamp
|
||||||
|
import world.phantasmal.psolib.Endianness
|
||||||
|
import world.phantasmal.psolib.buffer.Buffer
|
||||||
|
import world.phantasmal.psolib.cursor.cursor
|
||||||
|
import world.phantasmal.psoserv.encryption.BbCipher
|
||||||
|
import world.phantasmal.psoserv.encryption.Cipher
|
||||||
|
import world.phantasmal.psoserv.messages.*
|
||||||
|
import world.phantasmal.psoserv.utils.crc32Checksum
|
||||||
|
|
||||||
|
class AccountServer(
|
||||||
|
name: String,
|
||||||
|
bindPair: Inet4Pair,
|
||||||
|
) : GameServer<BbMessage>(name, bindPair) {
|
||||||
|
|
||||||
|
override val messageDescriptor = BbMessageDescriptor
|
||||||
|
|
||||||
|
override fun createCipher() = BbCipher()
|
||||||
|
|
||||||
|
override fun createClientReceiver(
|
||||||
|
sender: ClientSender<BbMessage>,
|
||||||
|
serverCipher: Cipher,
|
||||||
|
clientCipher: Cipher,
|
||||||
|
): ClientReceiver<BbMessage> = object : ClientReceiver<BbMessage> {
|
||||||
|
private val guildCardBuffer = Buffer.withSize(54672)
|
||||||
|
private var fileChunkNo = 0
|
||||||
|
private var guildCard: Int = -1
|
||||||
|
private var teamId: Int = -1
|
||||||
|
private var slot: Int = 0
|
||||||
|
private var charSelected: Boolean = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
sender.send(
|
||||||
|
BbMessage.InitEncryption(
|
||||||
|
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
|
||||||
|
serverCipher.key,
|
||||||
|
clientCipher.key,
|
||||||
|
),
|
||||||
|
encrypt = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(message: BbMessage): Boolean = when (message) {
|
||||||
|
is BbMessage.Authenticate -> {
|
||||||
|
// TODO: Actual authentication.
|
||||||
|
guildCard = message.guildCard
|
||||||
|
teamId = message.teamId
|
||||||
|
send(
|
||||||
|
BbMessage.AuthData(
|
||||||
|
BbAuthStatus.Success,
|
||||||
|
guildCard,
|
||||||
|
teamId,
|
||||||
|
slot,
|
||||||
|
charSelected,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.GetAccount -> {
|
||||||
|
// TODO: Send correct guild card number and team ID.
|
||||||
|
send(BbMessage.Account(0, 0))
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.CharSelect -> {
|
||||||
|
if (message.select) {
|
||||||
|
// Player has chosen a character.
|
||||||
|
// TODO: Verify slot.
|
||||||
|
if (slot in 0..3) {
|
||||||
|
slot = message.slot
|
||||||
|
charSelected = true
|
||||||
|
send(
|
||||||
|
BbMessage.AuthData(
|
||||||
|
BbAuthStatus.Success,
|
||||||
|
guildCard,
|
||||||
|
teamId,
|
||||||
|
slot,
|
||||||
|
charSelected,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
send(
|
||||||
|
BbMessage.CharSelectAck(slot, BbCharSelectStatus.Select)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
send(
|
||||||
|
BbMessage.CharSelectAck(slot, BbCharSelectStatus.Nonexistent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Player is previewing characters.
|
||||||
|
// TODO: Look up character data.
|
||||||
|
send(
|
||||||
|
BbMessage.CharData(
|
||||||
|
PsoCharacter(
|
||||||
|
slot = message.slot,
|
||||||
|
exp = 0,
|
||||||
|
level = 0,
|
||||||
|
guildCardString = "",
|
||||||
|
nameColor = 0,
|
||||||
|
model = 0,
|
||||||
|
nameColorChecksum = 0,
|
||||||
|
sectionId = message.slot,
|
||||||
|
characterClass = message.slot,
|
||||||
|
costume = 0,
|
||||||
|
skin = 0,
|
||||||
|
face = 0,
|
||||||
|
head = 0,
|
||||||
|
hair = 0,
|
||||||
|
hairRed = 0,
|
||||||
|
hairGreen = 0,
|
||||||
|
hairBlue = 0,
|
||||||
|
propX = 0.5,
|
||||||
|
propY = 0.5,
|
||||||
|
name = "Phantasmal ${message.slot}",
|
||||||
|
playTime = 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.Checksum -> {
|
||||||
|
// TODO: Checksum checking.
|
||||||
|
send(BbMessage.ChecksumAck(true))
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.GetGuildCardHeader -> {
|
||||||
|
send(
|
||||||
|
BbMessage.GuildCardHeader(
|
||||||
|
guildCardBuffer.size,
|
||||||
|
crc32Checksum(guildCardBuffer),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.GetGuildCardChunk -> {
|
||||||
|
if (message.cont) {
|
||||||
|
val offset = clamp(
|
||||||
|
message.chunkNo * MAX_CHUNK_SIZE,
|
||||||
|
min = 0,
|
||||||
|
max = guildCardBuffer.size,
|
||||||
|
)
|
||||||
|
val size = (guildCardBuffer.size - offset).coerceAtMost(MAX_CHUNK_SIZE)
|
||||||
|
|
||||||
|
send(
|
||||||
|
BbMessage.GuildCardChunk(
|
||||||
|
message.chunkNo,
|
||||||
|
guildCardBuffer.cursor(offset, size),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.GetFileList -> {
|
||||||
|
fileChunkNo = 0
|
||||||
|
|
||||||
|
send(BbMessage.FileList(FILE_LIST))
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.GetFileChunk -> {
|
||||||
|
val offset = (fileChunkNo * MAX_CHUNK_SIZE).coerceAtMost(
|
||||||
|
FILE_BUFFER.size
|
||||||
|
)
|
||||||
|
val size = (FILE_BUFFER.size - offset).coerceAtMost(
|
||||||
|
MAX_CHUNK_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
send(BbMessage.FileChunk(fileChunkNo, FILE_BUFFER.cursor(offset, size)))
|
||||||
|
|
||||||
|
if (offset + size < FILE_BUFFER.size) {
|
||||||
|
fileChunkNo++
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is BbMessage.Disconnect -> false
|
||||||
|
|
||||||
|
else -> unexpectedMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun send(message: BbMessage) {
|
||||||
|
sender.send(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_CHUNK_SIZE: Int = 0x6800
|
||||||
|
private val FILE_LIST: List<FileListEntry>
|
||||||
|
private val FILE_BUFFER: Buffer
|
||||||
|
|
||||||
|
init {
|
||||||
|
val filenames = listOf(
|
||||||
|
"BattleParamEntry.dat",
|
||||||
|
"BattleParamEntry_ep4.dat",
|
||||||
|
"BattleParamEntry_ep4_on.dat",
|
||||||
|
"BattleParamEntry_lab.dat",
|
||||||
|
"BattleParamEntry_lab_on.dat",
|
||||||
|
"BattleParamEntry_on.dat",
|
||||||
|
"ItemMagEdit.prs",
|
||||||
|
"ItemPMT.prs",
|
||||||
|
"PlyLevelTbl.prs",
|
||||||
|
)
|
||||||
|
val fileBuffers = mutableListOf<Buffer>()
|
||||||
|
|
||||||
|
val fileList = mutableListOf<FileListEntry>()
|
||||||
|
var offset = 0
|
||||||
|
|
||||||
|
for (filename in filenames) {
|
||||||
|
val data = Buffer.fromResource("/world/phantasmal/psoserv/$filename")
|
||||||
|
fileList.add(FileListEntry(data.size, crc32Checksum(data), offset, filename))
|
||||||
|
fileBuffers.add(data)
|
||||||
|
offset += data.size
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE_LIST = fileList
|
||||||
|
|
||||||
|
FILE_BUFFER = Buffer.withSize(offset, Endianness.Little)
|
||||||
|
offset = 0
|
||||||
|
|
||||||
|
for (data in fileBuffers) {
|
||||||
|
data.copyInto(FILE_BUFFER, destinationOffset = offset)
|
||||||
|
offset += data.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package world.phantasmal.psoserv.servers
|
||||||
|
|
||||||
|
import world.phantasmal.psoserv.encryption.BbCipher
|
||||||
|
import world.phantasmal.psoserv.encryption.Cipher
|
||||||
|
import world.phantasmal.psoserv.messages.BbAuthStatus
|
||||||
|
import world.phantasmal.psoserv.messages.BbMessage
|
||||||
|
import world.phantasmal.psoserv.messages.BbMessageDescriptor
|
||||||
|
import java.net.Inet4Address
|
||||||
|
|
||||||
|
class AuthServer(
|
||||||
|
name: String,
|
||||||
|
bindPair: Inet4Pair,
|
||||||
|
private val accountServerAddress: Inet4Address,
|
||||||
|
private val accountServerPort: Int,
|
||||||
|
) : GameServer<BbMessage>(name, bindPair) {
|
||||||
|
|
||||||
|
override val messageDescriptor = BbMessageDescriptor
|
||||||
|
|
||||||
|
override fun createCipher() = BbCipher()
|
||||||
|
|
||||||
|
override fun createClientReceiver(
|
||||||
|
sender: ClientSender<BbMessage>,
|
||||||
|
serverCipher: Cipher,
|
||||||
|
clientCipher: Cipher,
|
||||||
|
): ClientReceiver<BbMessage> = object : ClientReceiver<BbMessage> {
|
||||||
|
init {
|
||||||
|
sender.send(
|
||||||
|
BbMessage.InitEncryption(
|
||||||
|
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
|
||||||
|
serverCipher.key,
|
||||||
|
clientCipher.key,
|
||||||
|
),
|
||||||
|
encrypt = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(message: BbMessage): Boolean = when (message) {
|
||||||
|
is BbMessage.Authenticate -> {
|
||||||
|
// TODO: Actual authentication.
|
||||||
|
send(
|
||||||
|
BbMessage.AuthData(
|
||||||
|
BbAuthStatus.Success,
|
||||||
|
message.guildCard,
|
||||||
|
message.teamId,
|
||||||
|
slot = 0,
|
||||||
|
selected = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
send(
|
||||||
|
BbMessage.Redirect(accountServerAddress.address, accountServerPort)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Disconnect.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> unexpectedMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun send(message: BbMessage) {
|
||||||
|
sender.send(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers
|
|
||||||
|
|
||||||
import world.phantasmal.psoserv.encryption.BbCipher
|
|
||||||
import world.phantasmal.psoserv.messages.BbMessage
|
|
||||||
import world.phantasmal.psoserv.messages.BbMessageDescriptor
|
|
||||||
|
|
||||||
abstract class BbServer<StateType : ServerState<BbMessage, *, StateType>>(
|
|
||||||
name: String,
|
|
||||||
bindPair: Inet4Pair,
|
|
||||||
) : GameServer<BbMessage, StateType>(name, bindPair) {
|
|
||||||
|
|
||||||
override val messageDescriptor = BbMessageDescriptor
|
|
||||||
|
|
||||||
override fun createCipher() = BbCipher()
|
|
||||||
}
|
|
@ -5,12 +5,18 @@ import world.phantasmal.psoserv.messages.Message
|
|||||||
import world.phantasmal.psoserv.messages.MessageDescriptor
|
import world.phantasmal.psoserv.messages.MessageDescriptor
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
|
|
||||||
abstract class GameServer<MessageType, StateType>(
|
interface ClientReceiver<MessageType : Message> {
|
||||||
|
fun process(message: MessageType): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientSender<MessageType : Message> {
|
||||||
|
fun send(message: MessageType, encrypt: Boolean = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GameServer<MessageType : Message>(
|
||||||
name: String,
|
name: String,
|
||||||
bindPair: Inet4Pair,
|
bindPair: Inet4Pair,
|
||||||
) : Server(name, bindPair)
|
) : Server(name, bindPair) {
|
||||||
where MessageType : Message,
|
|
||||||
StateType : ServerState<MessageType, *, StateType> {
|
|
||||||
|
|
||||||
private var connectionCounter = 0
|
private var connectionCounter = 0
|
||||||
|
|
||||||
@ -18,28 +24,40 @@ abstract class GameServer<MessageType, StateType>(
|
|||||||
|
|
||||||
override fun clientConnected(clientSocket: Socket) {
|
override fun clientConnected(clientSocket: Socket) {
|
||||||
// Handle each client connection in its own thread.
|
// Handle each client connection in its own thread.
|
||||||
val thread = Thread {
|
val thread = Thread { GameClientHandler(clientSocket).listen() }
|
||||||
val handler = ClientHandler(clientSocket)
|
|
||||||
handler.initializeState()
|
|
||||||
handler.listen()
|
|
||||||
}
|
|
||||||
thread.name = "${name}_client_${connectionCounter++}"
|
thread.name = "${name}_client_${connectionCounter++}"
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun createCipher(): Cipher
|
protected abstract fun createCipher(): Cipher
|
||||||
|
|
||||||
protected abstract fun initializeState(
|
protected abstract fun createClientReceiver(
|
||||||
sender: SocketSender<MessageType>,
|
sender: ClientSender<MessageType>,
|
||||||
serverCipher: Cipher,
|
serverCipher: Cipher,
|
||||||
clientCipher: Cipher,
|
clientCipher: Cipher,
|
||||||
): StateType
|
): ClientReceiver<MessageType>
|
||||||
|
|
||||||
|
protected fun unexpectedMessage(message: MessageType): Boolean {
|
||||||
|
logger.debug { "Unexpected message: $message." }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class GameClientHandler(socket: Socket) :
|
||||||
|
SocketHandler<MessageType>(logger, socket) {
|
||||||
|
|
||||||
private inner class ClientHandler(socket: Socket) : SocketHandler<MessageType>(logger, socket) {
|
|
||||||
private val serverCipher = createCipher()
|
private val serverCipher = createCipher()
|
||||||
private val clientCipher = createCipher()
|
private val clientCipher = createCipher()
|
||||||
|
|
||||||
private var state: StateType? = null
|
private val handler: ClientReceiver<MessageType> =
|
||||||
|
createClientReceiver(
|
||||||
|
object : ClientSender<MessageType> {
|
||||||
|
override fun send(message: MessageType, encrypt: Boolean) {
|
||||||
|
sendMessage(message, encrypt)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serverCipher,
|
||||||
|
clientCipher,
|
||||||
|
)
|
||||||
|
|
||||||
override val messageDescriptor = this@GameServer.messageDescriptor
|
override val messageDescriptor = this@GameServer.messageDescriptor
|
||||||
|
|
||||||
@ -47,22 +65,15 @@ abstract class GameServer<MessageType, StateType>(
|
|||||||
override val readEncryptCipher: Cipher? = null
|
override val readEncryptCipher: Cipher? = null
|
||||||
override val writeEncryptCipher: Cipher = serverCipher
|
override val writeEncryptCipher: Cipher = serverCipher
|
||||||
|
|
||||||
fun initializeState() {
|
override fun processMessage(message: MessageType): ProcessResult =
|
||||||
state = initializeState(this, serverCipher, clientCipher)
|
if (handler.process(message)) {
|
||||||
}
|
ProcessResult.Ok
|
||||||
|
} else {
|
||||||
override fun processMessage(message: MessageType): ProcessResult {
|
|
||||||
state = state!!.process(message)
|
|
||||||
|
|
||||||
return if (state is FinalServerState) {
|
|
||||||
// Give the client some time to disconnect.
|
// Give the client some time to disconnect.
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
// Close the connection.
|
// Close the connection.
|
||||||
ProcessResult.Done
|
ProcessResult.Done
|
||||||
} else {
|
|
||||||
ProcessResult.Ok
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package world.phantasmal.psoserv.servers
|
||||||
|
|
||||||
|
import world.phantasmal.psoserv.encryption.Cipher
|
||||||
|
import world.phantasmal.psoserv.encryption.PcCipher
|
||||||
|
import world.phantasmal.psoserv.messages.PcMessage
|
||||||
|
import world.phantasmal.psoserv.messages.PcMessageDescriptor
|
||||||
|
|
||||||
|
class PatchServer(
|
||||||
|
name: String,
|
||||||
|
bindPair: Inet4Pair,
|
||||||
|
private val welcomeMessage: String,
|
||||||
|
) : GameServer<PcMessage>(name, bindPair) {
|
||||||
|
|
||||||
|
override val messageDescriptor = PcMessageDescriptor
|
||||||
|
|
||||||
|
override fun createCipher() = PcCipher()
|
||||||
|
|
||||||
|
override fun createClientReceiver(
|
||||||
|
sender: ClientSender<PcMessage>,
|
||||||
|
serverCipher: Cipher,
|
||||||
|
clientCipher: Cipher,
|
||||||
|
): ClientReceiver<PcMessage> = object : ClientReceiver<PcMessage> {
|
||||||
|
init {
|
||||||
|
sender.send(
|
||||||
|
PcMessage.InitEncryption(
|
||||||
|
"Patch Server. Copyright SonicTeam, LTD. 2001",
|
||||||
|
serverCipher.key,
|
||||||
|
clientCipher.key,
|
||||||
|
),
|
||||||
|
encrypt = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun process(message: PcMessage): Boolean = when (message) {
|
||||||
|
is PcMessage.InitEncryption -> {
|
||||||
|
send(PcMessage.Login())
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is PcMessage.Login -> {
|
||||||
|
send(PcMessage.WelcomeMessage(welcomeMessage))
|
||||||
|
send(PcMessage.PatchListStart())
|
||||||
|
send(PcMessage.PatchListEnd())
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
is PcMessage.PatchListOk -> {
|
||||||
|
send(PcMessage.PatchDone())
|
||||||
|
|
||||||
|
// Disconnect.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> unexpectedMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun send(message: PcMessage) {
|
||||||
|
sender.send(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers
|
|
||||||
|
|
||||||
import mu.KLogger
|
|
||||||
import world.phantasmal.psoserv.messages.Message
|
|
||||||
|
|
||||||
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 {
|
|
||||||
ctx.logger.debug { "Transitioning to ${this::class.simpleName} state." }
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun process(message: MessageType): Self
|
|
||||||
|
|
||||||
protected fun unexpectedMessage(message: MessageType): Self {
|
|
||||||
ctx.logger.debug { "Unexpected message: $message." }
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return this as Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FinalServerState
|
|
@ -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
|
||||||
|
|
||||||
@ -223,7 +223,7 @@ abstract class SocketHandler<MessageType : Message>(
|
|||||||
socket.close()
|
socket.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendMessage(message: MessageType, encrypt: Boolean) {
|
fun sendMessage(message: MessageType, encrypt: Boolean) {
|
||||||
logger.trace {
|
logger.trace {
|
||||||
"Sending $message${if (encrypt) "" else " (unencrypted)"}."
|
"Sending $message${if (encrypt) "" else " (unencrypted)"}."
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers
|
|
||||||
|
|
||||||
import world.phantasmal.psoserv.messages.Message
|
|
||||||
|
|
||||||
interface SocketSender<MessageType : Message> {
|
|
||||||
fun sendMessage(message: MessageType, encrypt: Boolean = true)
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers.account
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
class AccountServer(
|
|
||||||
name: String,
|
|
||||||
bindPair: Inet4Pair,
|
|
||||||
) : BbServer<AccountState>(name, bindPair) {
|
|
||||||
|
|
||||||
override fun initializeState(
|
|
||||||
sender: SocketSender<BbMessage>,
|
|
||||||
serverCipher: Cipher,
|
|
||||||
clientCipher: Cipher,
|
|
||||||
): AccountState {
|
|
||||||
val ctx = AccountContext(logger, sender)
|
|
||||||
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.InitEncryption(
|
|
||||||
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
|
|
||||||
serverCipher.key,
|
|
||||||
clientCipher.key,
|
|
||||||
),
|
|
||||||
encrypt = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
return AccountState.Authentication(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,250 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers.account
|
|
||||||
|
|
||||||
import mu.KLogger
|
|
||||||
import world.phantasmal.core.math.clamp
|
|
||||||
import world.phantasmal.psolib.Endianness
|
|
||||||
import world.phantasmal.psolib.buffer.Buffer
|
|
||||||
import world.phantasmal.psolib.cursor.cursor
|
|
||||||
import world.phantasmal.psoserv.messages.*
|
|
||||||
import world.phantasmal.psoserv.servers.FinalServerState
|
|
||||||
import world.phantasmal.psoserv.servers.ServerState
|
|
||||||
import world.phantasmal.psoserv.servers.ServerStateContext
|
|
||||||
import world.phantasmal.psoserv.servers.SocketSender
|
|
||||||
import world.phantasmal.psoserv.utils.crc32Checksum
|
|
||||||
|
|
||||||
class AccountContext(
|
|
||||||
logger: KLogger,
|
|
||||||
socketSender: SocketSender<BbMessage>,
|
|
||||||
var guildCard: Int = -1,
|
|
||||||
var teamId: Int = -1,
|
|
||||||
var slot: Int = 0,
|
|
||||||
var charSelected: Boolean = false,
|
|
||||||
) : ServerStateContext<BbMessage>(logger, socketSender)
|
|
||||||
|
|
||||||
sealed class AccountState(ctx: AccountContext) :
|
|
||||||
ServerState<BbMessage, AccountContext, AccountState>(ctx) {
|
|
||||||
|
|
||||||
class Authentication(ctx: AccountContext) : AccountState(ctx) {
|
|
||||||
override fun process(message: BbMessage): AccountState =
|
|
||||||
if (message is BbMessage.Authenticate) {
|
|
||||||
// TODO: Actual authentication.
|
|
||||||
ctx.guildCard = message.guildCard
|
|
||||||
ctx.teamId = message.teamId
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.AuthData(
|
|
||||||
BbAuthStatus.Success,
|
|
||||||
ctx.guildCard,
|
|
||||||
ctx.teamId,
|
|
||||||
ctx.slot,
|
|
||||||
ctx.charSelected,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Account(ctx)
|
|
||||||
} else {
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Account(ctx: AccountContext) : AccountState(ctx) {
|
|
||||||
override fun process(message: BbMessage): AccountState =
|
|
||||||
when (message) {
|
|
||||||
is BbMessage.GetAccount -> {
|
|
||||||
// TODO: Send correct guild card number and team ID.
|
|
||||||
ctx.send(BbMessage.Account(0, 0))
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
is BbMessage.CharSelect -> {
|
|
||||||
if (message.select) {
|
|
||||||
// TODO: Verify slot.
|
|
||||||
if (ctx.slot in 0..3) {
|
|
||||||
ctx.slot = message.slot
|
|
||||||
ctx.charSelected = true
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.AuthData(
|
|
||||||
BbAuthStatus.Success,
|
|
||||||
ctx.guildCard,
|
|
||||||
ctx.teamId,
|
|
||||||
ctx.slot,
|
|
||||||
ctx.charSelected,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.CharSelectAck(ctx.slot, BbCharSelectStatus.Select)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.CharSelectAck(ctx.slot, BbCharSelectStatus.Nonexistent)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Look up character data.
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.CharData(
|
|
||||||
PsoCharacter(
|
|
||||||
slot = message.slot,
|
|
||||||
exp = 0,
|
|
||||||
level = 0,
|
|
||||||
guildCardString = "",
|
|
||||||
nameColor = 0,
|
|
||||||
model = 0,
|
|
||||||
nameColorChecksum = 0,
|
|
||||||
sectionId = message.slot,
|
|
||||||
characterClass = message.slot,
|
|
||||||
costume = 0,
|
|
||||||
skin = 0,
|
|
||||||
face = 0,
|
|
||||||
head = 0,
|
|
||||||
hair = 0,
|
|
||||||
hairRed = 0,
|
|
||||||
hairGreen = 0,
|
|
||||||
hairBlue = 0,
|
|
||||||
propX = 0.5,
|
|
||||||
propY = 0.5,
|
|
||||||
name = "Phantasmal ${message.slot}",
|
|
||||||
playTime = 0,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
is BbMessage.Checksum -> {
|
|
||||||
// TODO: Checksum checking.
|
|
||||||
ctx.send(BbMessage.ChecksumAck(true))
|
|
||||||
|
|
||||||
GuildCardData(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
is BbMessage.Disconnect -> Final(ctx)
|
|
||||||
|
|
||||||
else -> unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GuildCardData(ctx: AccountContext) : AccountState(ctx) {
|
|
||||||
private val guildCardBuffer = Buffer.withSize(54672)
|
|
||||||
|
|
||||||
override fun process(message: BbMessage): AccountState =
|
|
||||||
when (message) {
|
|
||||||
is BbMessage.GetGuildCardHeader -> {
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.GuildCardHeader(
|
|
||||||
guildCardBuffer.size,
|
|
||||||
crc32Checksum(guildCardBuffer),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
is BbMessage.GetGuildCardChunk -> {
|
|
||||||
if (message.cont) {
|
|
||||||
val offset = clamp(
|
|
||||||
message.chunkNo * MAX_CHUNK_SIZE,
|
|
||||||
min = 0,
|
|
||||||
max = guildCardBuffer.size,
|
|
||||||
)
|
|
||||||
val size = (guildCardBuffer.size - offset).coerceAtMost(MAX_CHUNK_SIZE)
|
|
||||||
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.GuildCardChunk(
|
|
||||||
message.chunkNo,
|
|
||||||
guildCardBuffer.cursor(offset, size),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
this
|
|
||||||
} else {
|
|
||||||
DownloadFiles(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MAX_CHUNK_SIZE: Int = 0x6800
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadFiles(ctx: AccountContext) : AccountState(ctx) {
|
|
||||||
private var fileChunkNo = 0
|
|
||||||
|
|
||||||
override fun process(message: BbMessage): AccountState =
|
|
||||||
when (message) {
|
|
||||||
is BbMessage.GetFileList -> {
|
|
||||||
ctx.send(BbMessage.FileList(FILE_LIST))
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
is BbMessage.GetFileChunk -> {
|
|
||||||
val offset = (fileChunkNo * MAX_CHUNK_SIZE).coerceAtMost(FILE_BUFFER.size)
|
|
||||||
val size = (FILE_BUFFER.size - offset).coerceAtMost(MAX_CHUNK_SIZE)
|
|
||||||
|
|
||||||
ctx.send(BbMessage.FileChunk(fileChunkNo, FILE_BUFFER.cursor(offset, size)))
|
|
||||||
|
|
||||||
if (offset + size < FILE_BUFFER.size) {
|
|
||||||
fileChunkNo++
|
|
||||||
}
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
is BbMessage.Disconnect -> Final(ctx)
|
|
||||||
|
|
||||||
else -> unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val MAX_CHUNK_SIZE: Int = 0x6800
|
|
||||||
private val FILE_LIST: List<FileListEntry>
|
|
||||||
private val FILE_BUFFER: Buffer
|
|
||||||
|
|
||||||
init {
|
|
||||||
val filenames = listOf(
|
|
||||||
"BattleParamEntry.dat",
|
|
||||||
"BattleParamEntry_ep4.dat",
|
|
||||||
"BattleParamEntry_ep4_on.dat",
|
|
||||||
"BattleParamEntry_lab.dat",
|
|
||||||
"BattleParamEntry_lab_on.dat",
|
|
||||||
"BattleParamEntry_on.dat",
|
|
||||||
"ItemMagEdit.prs",
|
|
||||||
"ItemPMT.prs",
|
|
||||||
"PlyLevelTbl.prs",
|
|
||||||
)
|
|
||||||
val fileBuffers = mutableListOf<Buffer>()
|
|
||||||
|
|
||||||
val fileList = mutableListOf<FileListEntry>()
|
|
||||||
var offset = 0
|
|
||||||
|
|
||||||
for (filename in filenames) {
|
|
||||||
val data = Buffer.fromResource("/world/phantasmal/psoserv/$filename")
|
|
||||||
fileList.add(FileListEntry(data.size, crc32Checksum(data), offset, filename))
|
|
||||||
fileBuffers.add(data)
|
|
||||||
offset += data.size
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE_LIST = fileList
|
|
||||||
|
|
||||||
FILE_BUFFER = Buffer.withSize(offset, Endianness.Little)
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
for (data in fileBuffers) {
|
|
||||||
data.copyInto(FILE_BUFFER, destinationOffset = offset)
|
|
||||||
offset += data.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Final(ctx: AccountContext) : AccountState(ctx), FinalServerState {
|
|
||||||
override fun process(message: BbMessage): AccountState =
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers.auth
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
class AuthServer(
|
|
||||||
name: String,
|
|
||||||
bindPair: Inet4Pair,
|
|
||||||
private val dataServerAddress: Inet4Address,
|
|
||||||
private val dataServerPort: Int,
|
|
||||||
) : BbServer<AuthState>(name, bindPair) {
|
|
||||||
|
|
||||||
override fun initializeState(
|
|
||||||
sender: SocketSender<BbMessage>,
|
|
||||||
serverCipher: Cipher,
|
|
||||||
clientCipher: Cipher,
|
|
||||||
): AuthState {
|
|
||||||
val ctx = AuthContext(logger, sender, dataServerAddress.address, dataServerPort)
|
|
||||||
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.InitEncryption(
|
|
||||||
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
|
|
||||||
serverCipher.key,
|
|
||||||
clientCipher.key,
|
|
||||||
),
|
|
||||||
encrypt = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
return AuthState.Authentication(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers.auth
|
|
||||||
|
|
||||||
import mu.KLogger
|
|
||||||
import world.phantasmal.psoserv.messages.BbAuthStatus
|
|
||||||
import world.phantasmal.psoserv.messages.BbMessage
|
|
||||||
import world.phantasmal.psoserv.servers.FinalServerState
|
|
||||||
import world.phantasmal.psoserv.servers.ServerState
|
|
||||||
import world.phantasmal.psoserv.servers.ServerStateContext
|
|
||||||
import world.phantasmal.psoserv.servers.SocketSender
|
|
||||||
|
|
||||||
class AuthContext(
|
|
||||||
logger: KLogger,
|
|
||||||
socketSender: SocketSender<BbMessage>,
|
|
||||||
val accountServerAddress: ByteArray,
|
|
||||||
val accountServerPort: Int,
|
|
||||||
) : ServerStateContext<BbMessage>(logger, socketSender)
|
|
||||||
|
|
||||||
sealed class AuthState(ctx: AuthContext) :
|
|
||||||
ServerState<BbMessage, AuthContext, AuthState>(ctx) {
|
|
||||||
|
|
||||||
class Authentication(ctx: AuthContext) : AuthState(ctx) {
|
|
||||||
override fun process(message: BbMessage): AuthState =
|
|
||||||
if (message is BbMessage.Authenticate) {
|
|
||||||
// TODO: Actual authentication.
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.AuthData(
|
|
||||||
BbAuthStatus.Success,
|
|
||||||
message.guildCard,
|
|
||||||
message.teamId,
|
|
||||||
slot = 0,
|
|
||||||
selected = false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ctx.send(
|
|
||||||
BbMessage.Redirect(
|
|
||||||
ctx.accountServerAddress,
|
|
||||||
ctx.accountServerPort,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Final(ctx)
|
|
||||||
} else {
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Final(ctx: AuthContext) : AuthState(ctx), FinalServerState {
|
|
||||||
override fun process(message: BbMessage): AuthState =
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package world.phantasmal.psoserv.servers.patch
|
|
||||||
|
|
||||||
import world.phantasmal.psoserv.encryption.Cipher
|
|
||||||
import world.phantasmal.psoserv.encryption.PcCipher
|
|
||||||
import world.phantasmal.psoserv.messages.PcMessage
|
|
||||||
import world.phantasmal.psoserv.messages.PcMessageDescriptor
|
|
||||||
import world.phantasmal.psoserv.servers.GameServer
|
|
||||||
import world.phantasmal.psoserv.servers.Inet4Pair
|
|
||||||
import world.phantasmal.psoserv.servers.SocketSender
|
|
||||||
|
|
||||||
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: SocketSender<PcMessage>,
|
|
||||||
serverCipher: Cipher,
|
|
||||||
clientCipher: Cipher,
|
|
||||||
): PatchState {
|
|
||||||
val ctx = PatchContext(logger, sender, welcomeMessage)
|
|
||||||
|
|
||||||
ctx.send(
|
|
||||||
PcMessage.InitEncryption(
|
|
||||||
"Patch Server. Copyright SonicTeam, LTD. 2001",
|
|
||||||
serverCipher.key,
|
|
||||||
clientCipher.key,
|
|
||||||
),
|
|
||||||
encrypt = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
return PatchState.Welcome(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
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.ServerState
|
|
||||||
import world.phantasmal.psoserv.servers.ServerStateContext
|
|
||||||
import world.phantasmal.psoserv.servers.SocketSender
|
|
||||||
|
|
||||||
class PatchContext(
|
|
||||||
logger: KLogger,
|
|
||||||
socketSender: SocketSender<PcMessage>,
|
|
||||||
val welcomeMessage: String,
|
|
||||||
) : ServerStateContext<PcMessage>(logger, socketSender)
|
|
||||||
|
|
||||||
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())
|
|
||||||
|
|
||||||
Login(ctx)
|
|
||||||
} else {
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Login(ctx: PatchContext) : PatchState(ctx) {
|
|
||||||
override fun process(message: PcMessage): PatchState =
|
|
||||||
if (message is PcMessage.Login) {
|
|
||||||
ctx.send(PcMessage.WelcomeMessage(ctx.welcomeMessage))
|
|
||||||
ctx.send(PcMessage.PatchListStart())
|
|
||||||
ctx.send(PcMessage.PatchListEnd())
|
|
||||||
|
|
||||||
PatchListDone(ctx)
|
|
||||||
} else {
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PatchListDone(ctx: PatchContext) : PatchState(ctx) {
|
|
||||||
override fun process(message: PcMessage): PatchState =
|
|
||||||
if (message is PcMessage.PatchListOk) {
|
|
||||||
ctx.send(PcMessage.PatchDone())
|
|
||||||
|
|
||||||
Final(ctx)
|
|
||||||
} else {
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Final(ctx: PatchContext) : PatchState(ctx), FinalServerState {
|
|
||||||
override fun process(message: PcMessage): PatchState =
|
|
||||||
unexpectedMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user