Simplified psoserv architecture.

This commit is contained in:
Daan Vanden Bosch 2021-08-01 20:34:51 +02:00
parent f6e0ed06a9
commit 810c1cb549
15 changed files with 412 additions and 555 deletions

View File

@ -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<Server>()
@ -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." }

View File

@ -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
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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()
}

View File

@ -5,12 +5,18 @@ import world.phantasmal.psoserv.messages.Message
import world.phantasmal.psoserv.messages.MessageDescriptor
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,
bindPair: Inet4Pair,
) : Server(name, bindPair)
where MessageType : Message,
StateType : ServerState<MessageType, *, StateType> {
) : Server(name, bindPair) {
private var connectionCounter = 0
@ -18,28 +24,40 @@ abstract class GameServer<MessageType, StateType>(
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<MessageType>,
protected abstract fun createClientReceiver(
sender: ClientSender<MessageType>,
serverCipher: 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 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
@ -47,22 +65,15 @@ abstract class GameServer<MessageType, StateType>(
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
}
}
}
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -15,7 +15,7 @@ import kotlin.math.min
abstract class SocketHandler<MessageType : Message>(
protected val logger: KLogger,
private val socket: Socket,
) : SocketSender<MessageType> {
) {
private val sockName: String = "${socket.remoteSocketAddress}"
private val headerSize: Int get() = messageDescriptor.headerSize
@ -223,7 +223,7 @@ abstract class SocketHandler<MessageType : Message>(
socket.close()
}
override fun sendMessage(message: MessageType, encrypt: Boolean) {
fun sendMessage(message: MessageType, encrypt: Boolean) {
logger.trace {
"Sending $message${if (encrypt) "" else " (unencrypted)"}."
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}