diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt index b8512312..ad8086bb 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/Main.kt @@ -11,9 +11,6 @@ import world.phantasmal.psoserv.messages.Message import world.phantasmal.psoserv.messages.MessageDescriptor import world.phantasmal.psoserv.messages.PcMessageDescriptor 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.net.Inet4Address @@ -21,7 +18,7 @@ import java.net.Inet4Address private val DEFAULT_ADDRESS: Inet4Address = inet4Loopback() private const val DEFAULT_PATCH_PORT: Int = 11_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") @@ -87,8 +84,8 @@ private class PhantasmalServer( private fun initialize(config: Config): PhantasmalServer { val defaultAddress = config.address?.let(::inet4Address) ?: DEFAULT_ADDRESS - val dataAddress = config.account?.address?.let(::inet4Address) ?: defaultAddress - val dataPort = config.account?.port ?: DEFAULT_DATA_PORT + val accountAddress = config.account?.address?.let(::inet4Address) ?: defaultAddress + val accountPort = config.account?.port ?: DEFAULT_ACCOUNT_PORT val servers = mutableListOf() @@ -124,8 +121,8 @@ private fun initialize(config: Config): PhantasmalServer { AuthServer( name = "auth", bindPair, - dataServerAddress = dataAddress, - dataServerPort = dataPort, + accountServerAddress = accountAddress, + accountServerPort = accountPort, ) ) } @@ -133,7 +130,7 @@ private fun initialize(config: Config): PhantasmalServer { if (config.account == null && run || config.account?.run == true) { val bindPair = Inet4Pair( 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." } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt new file mode 100644 index 00000000..07e4e743 --- /dev/null +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AccountServer.kt @@ -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(name, bindPair) { + + override val messageDescriptor = BbMessageDescriptor + + override fun createCipher() = BbCipher() + + override fun createClientReceiver( + sender: ClientSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): ClientReceiver = object : ClientReceiver { + 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 + 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() + + val fileList = mutableListOf() + 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 + } + } + } +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt new file mode 100644 index 00000000..9fca3585 --- /dev/null +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/AuthServer.kt @@ -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(name, bindPair) { + + override val messageDescriptor = BbMessageDescriptor + + override fun createCipher() = BbCipher() + + override fun createClientReceiver( + sender: ClientSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): ClientReceiver = object : ClientReceiver { + 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) + } + } +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt deleted file mode 100644 index 5390ffd9..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/BbServer.kt +++ /dev/null @@ -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>( - name: String, - bindPair: Inet4Pair, -) : GameServer(name, bindPair) { - - override val messageDescriptor = BbMessageDescriptor - - override fun createCipher() = BbCipher() -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt index 0ef06090..085a2319 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/GameServer.kt @@ -5,12 +5,18 @@ import world.phantasmal.psoserv.messages.Message import world.phantasmal.psoserv.messages.MessageDescriptor import java.net.Socket -abstract class GameServer( +interface ClientReceiver { + fun process(message: MessageType): Boolean +} + +interface ClientSender { + fun send(message: MessageType, encrypt: Boolean = true) +} + +abstract class GameServer( name: String, bindPair: Inet4Pair, -) : Server(name, bindPair) - where MessageType : Message, - StateType : ServerState { +) : Server(name, bindPair) { private var connectionCounter = 0 @@ -18,28 +24,40 @@ abstract class GameServer( override fun clientConnected(clientSocket: Socket) { // Handle each client connection in its own thread. - val thread = Thread { - val handler = ClientHandler(clientSocket) - handler.initializeState() - handler.listen() - } + val thread = Thread { GameClientHandler(clientSocket).listen() } thread.name = "${name}_client_${connectionCounter++}" thread.start() } protected abstract fun createCipher(): Cipher - protected abstract fun initializeState( - sender: SocketSender, + protected abstract fun createClientReceiver( + sender: ClientSender, serverCipher: Cipher, clientCipher: Cipher, - ): StateType + ): ClientReceiver + + protected fun unexpectedMessage(message: MessageType): Boolean { + logger.debug { "Unexpected message: $message." } + return true + } + + private inner class GameClientHandler(socket: Socket) : + SocketHandler(logger, socket) { - private inner class ClientHandler(socket: Socket) : SocketHandler(logger, socket) { private val serverCipher = createCipher() private val clientCipher = createCipher() - private var state: StateType? = null + private val handler: ClientReceiver = + createClientReceiver( + object : ClientSender { + override fun send(message: MessageType, encrypt: Boolean) { + sendMessage(message, encrypt) + } + }, + serverCipher, + clientCipher, + ) override val messageDescriptor = this@GameServer.messageDescriptor @@ -47,22 +65,15 @@ abstract class GameServer( 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) { + override fun processMessage(message: MessageType): ProcessResult = + if (handler.process(message)) { + ProcessResult.Ok + } else { // Give the client some time to disconnect. Thread.sleep(100) // Close the connection. ProcessResult.Done - } else { - ProcessResult.Ok } - } } } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt new file mode 100644 index 00000000..feef5f9b --- /dev/null +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/PatchServer.kt @@ -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(name, bindPair) { + + override val messageDescriptor = PcMessageDescriptor + + override fun createCipher() = PcCipher() + + override fun createClientReceiver( + sender: ClientSender, + serverCipher: Cipher, + clientCipher: Cipher, + ): ClientReceiver = object : ClientReceiver { + 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) + } + } +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt deleted file mode 100644 index bb37a4fe..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/ServerState.kt +++ /dev/null @@ -1,34 +0,0 @@ -package world.phantasmal.psoserv.servers - -import mu.KLogger -import world.phantasmal.psoserv.messages.Message - -abstract class ServerStateContext( - val logger: KLogger, - private val socketSender: SocketSender, -) { - fun send(message: MessageType, encrypt: Boolean = true) { - socketSender.sendMessage(message, encrypt) - } -} - -abstract class ServerState( - protected val ctx: ContextType, -) where MessageType : Message, - ContextType : ServerStateContext, - Self : ServerState { - - init { - 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 diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt index 103435ee..22b9b4d8 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketHandler.kt @@ -15,7 +15,7 @@ import kotlin.math.min abstract class SocketHandler( protected val logger: KLogger, private val socket: Socket, -) : SocketSender { +) { private val sockName: String = "${socket.remoteSocketAddress}" private val headerSize: Int get() = messageDescriptor.headerSize @@ -223,7 +223,7 @@ abstract class SocketHandler( socket.close() } - override fun sendMessage(message: MessageType, encrypt: Boolean) { + fun sendMessage(message: MessageType, encrypt: Boolean) { logger.trace { "Sending $message${if (encrypt) "" else " (unencrypted)"}." } diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt deleted file mode 100644 index 820f8f26..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/SocketSender.kt +++ /dev/null @@ -1,7 +0,0 @@ -package world.phantasmal.psoserv.servers - -import world.phantasmal.psoserv.messages.Message - -interface SocketSender { - fun sendMessage(message: MessageType, encrypt: Boolean = true) -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountServer.kt deleted file mode 100644 index 08523ad5..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountServer.kt +++ /dev/null @@ -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(name, bindPair) { - - override fun initializeState( - sender: SocketSender, - 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) - } -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountState.kt deleted file mode 100644 index 0aa23c22..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountState.kt +++ /dev/null @@ -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, - var guildCard: Int = -1, - var teamId: Int = -1, - var slot: Int = 0, - var charSelected: Boolean = false, -) : ServerStateContext(logger, socketSender) - -sealed class AccountState(ctx: AccountContext) : - ServerState(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 - 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() - - val fileList = mutableListOf() - 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) - } -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthServer.kt deleted file mode 100644 index e4e2703a..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthServer.kt +++ /dev/null @@ -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(name, bindPair) { - - override fun initializeState( - sender: SocketSender, - 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) - } -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthState.kt deleted file mode 100644 index 162b22fc..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthState.kt +++ /dev/null @@ -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, - val accountServerAddress: ByteArray, - val accountServerPort: Int, -) : ServerStateContext(logger, socketSender) - -sealed class AuthState(ctx: AuthContext) : - ServerState(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) - } -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt deleted file mode 100644 index 7d6eff52..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchServer.kt +++ /dev/null @@ -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(name, bindPair) { - - override val messageDescriptor = PcMessageDescriptor - - override fun createCipher() = PcCipher() - - override fun initializeState( - sender: SocketSender, - serverCipher: Cipher, - clientCipher: Cipher, - ): PatchState { - val ctx = PatchContext(logger, sender, welcomeMessage) - - ctx.send( - PcMessage.InitEncryption( - "Patch Server. Copyright SonicTeam, LTD. 2001", - serverCipher.key, - clientCipher.key, - ), - encrypt = false, - ) - - return PatchState.Welcome(ctx) - } -} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt deleted file mode 100644 index 68a33320..00000000 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/patch/PatchState.kt +++ /dev/null @@ -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, - val welcomeMessage: String, -) : ServerStateContext(logger, socketSender) - -sealed class PatchState(ctx: PatchContext) : ServerState(ctx) { - class Welcome(ctx: PatchContext) : PatchState(ctx) { - override fun process(message: PcMessage): PatchState = - if (message is PcMessage.InitEncryption) { - ctx.send(PcMessage.Login()) - - 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) - } -}