From f6e0ed06a9f0d1a0c392d2b601b25a3657cbfae8 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Sun, 1 Aug 2021 17:49:53 +0200 Subject: [PATCH] Character selection on psoserv now works. --- .../phantasmal/psoserv/messages/BbMessages.kt | 175 ++++++++++-------- .../phantasmal/psoserv/messages/PcMessages.kt | 2 +- .../psoserv/servers/account/AccountState.kt | 134 ++++++++------ .../psoserv/servers/auth/AuthState.kt | 8 +- 4 files changed, 182 insertions(+), 137 deletions(-) diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt index fd87bf0c..83f8d1c4 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/BbMessages.kt @@ -12,69 +12,6 @@ const val BB_HEADER_SIZE: Int = 8 const val BB_MSG_SIZE_POS: Int = 0 const val BB_MSG_CODE_POS: Int = 2 -enum class BbAuthenticationStatus { - Success, Error, UnknownUser -} - -class PsoCharacter( - val slot: Int, - val exp: Int, - val level: Int, - val guildCardString: String, - val nameColor: Int, - val model: Int, - val nameColorChecksum: Int, - val sectionId: Int, - val characterClass: Int, - val costume: Int, - val skin: Int, - val face: Int, - val head: Int, - val hair: Int, - val hairRed: Int, - val hairGreen: Int, - val hairBlue: Int, - val propX: Double, - val propY: Double, - val name: String, - val playTime: Int, -) { - init { - require(slot in 0..3) - require(exp >= 0) - require(level in 0..199) - require(guildCardString.length <= 16) - require(name.length <= 16) - require(playTime >= 0) - } -} - -class GuildCardEntry( - val playerTag: Int, - val serialNumber: Int, - val name: String, - val description: String, - val sectionId: Int, - val characterClass: Int, -) - -class GuildCard( - val entries: List -) - -class FileListEntry( - val size: Int, - val checksum: Int, - val offset: Int, - val filename: String, -) { - init { - require(size > 0) - require(offset >= 0) - require(filename.length <= 64) - } -} - object BbMessageDescriptor : MessageDescriptor { override val headerSize: Int = BB_HEADER_SIZE @@ -97,11 +34,12 @@ object BbMessageDescriptor : MessageDescriptor { 0x03DC -> BbMessage.GetGuildCardChunk(buffer) 0x00E0 -> BbMessage.GetAccount(buffer) 0x00E2 -> BbMessage.Account(buffer) - 0x00E3 -> BbMessage.CharacterSelect(buffer) - 0x00E5 -> BbMessage.CharacterSelectResponse(buffer) - 0x00E6 -> BbMessage.AuthenticationResponse(buffer) + 0x00E3 -> BbMessage.CharSelect(buffer) + 0x00E4 -> BbMessage.CharSelectAck(buffer) + 0x00E5 -> BbMessage.CharData(buffer) + 0x00E6 -> BbMessage.AuthData(buffer) 0x01E8 -> BbMessage.Checksum(buffer) - 0x02E8 -> BbMessage.ChecksumResponse(buffer) + 0x02E8 -> BbMessage.ChecksumAck(buffer) 0x03E8 -> BbMessage.GetGuildCardHeader(buffer) 0x01EB -> BbMessage.FileList(buffer) 0x02EB -> BbMessage.FileChunk(buffer) @@ -243,12 +181,30 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_ } // 0x00E3 - class CharacterSelect(buffer: Buffer) : BbMessage(buffer) { + class CharSelect(buffer: Buffer) : BbMessage(buffer) { val slot: Int get() = uByte(0).toInt() val select: Boolean get() = byte(4).toInt() != 0 + + override fun toString(): String = + messageString("slot" to slot, "select" to select) } - class CharacterSelectResponse(buffer: Buffer) : BbMessage(buffer) { + class CharSelectAck(buffer: Buffer) : BbMessage(buffer) { + constructor(slot: Int, status: BbCharSelectStatus) : this( + buf(0x00E4, 8) { + writeInt(slot) + writeInt( + when (status) { + BbCharSelectStatus.Update -> 0 + BbCharSelectStatus.Select -> 1 + BbCharSelectStatus.Nonexistent -> 2 + } + ) + } + ) + } + + class CharData(buffer: Buffer) : BbMessage(buffer) { constructor(char: PsoCharacter) : this( buf(0x00E5, 128) { writeInt(char.slot) @@ -281,24 +237,33 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_ ) } - class AuthenticationResponse(buffer: Buffer) : BbMessage(buffer) { - constructor(status: BbAuthenticationStatus, guildCard: Int, teamId: Int) : this( + class AuthData(buffer: Buffer) : BbMessage(buffer) { + constructor( + status: BbAuthStatus, + guildCard: Int, + teamId: Int, + slot: Int, + selected: Boolean, + ) : this( buf(0x00E6, 60) { writeInt( when (status) { - BbAuthenticationStatus.Success -> 0 - BbAuthenticationStatus.Error -> 1 - BbAuthenticationStatus.UnknownUser -> 8 + BbAuthStatus.Success -> 0 + BbAuthStatus.Error -> 1 + BbAuthStatus.Nonexistent -> 8 } ) writeInt(0x10000) writeInt(guildCard) writeInt(teamId) writeInt( - if (status == BbAuthenticationStatus.Success) (0xDEADBEEF).toInt() else 0 + if (status == BbAuthStatus.Success) (0xDEADBEEF).toInt() else 0 ) - // 36 Bytes of unknown data. - repeat(9) { writeInt(0) } + writeByte(slot.toByte()) + writeByte(if (selected) 1 else 0) + // 34 Bytes of unknown data. + writeShort(0) + repeat(8) { writeInt(0) } writeInt(0x102) } ) @@ -315,7 +280,7 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_ val checksum: Int get() = int(0) } - class ChecksumResponse(buffer: Buffer) : BbMessage(buffer) { + class ChecksumAck(buffer: Buffer) : BbMessage(buffer) { constructor(success: Boolean) : this( buf(0x02E8, 4) { writeInt(if (success) 1 else 0) @@ -360,7 +325,7 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_ class Unknown(buffer: Buffer) : BbMessage(buffer) companion object { - protected fun buf( + private fun buf( code: Int, bodySize: Int = 0, flags: Int = 0, @@ -385,3 +350,55 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_ } } } + +enum class BbAuthStatus { + Success, Error, Nonexistent +} + +class PsoCharacter( + val slot: Int, + val exp: Int, + val level: Int, + val guildCardString: String, + val nameColor: Int, + val model: Int, + val nameColorChecksum: Int, + val sectionId: Int, + val characterClass: Int, + val costume: Int, + val skin: Int, + val face: Int, + val head: Int, + val hair: Int, + val hairRed: Int, + val hairGreen: Int, + val hairBlue: Int, + val propX: Double, + val propY: Double, + val name: String, + val playTime: Int, +) + +class GuildCardEntry( + val playerTag: Int, + val serialNumber: Int, + val name: String, + val description: String, + val sectionId: Int, + val characterClass: Int, +) + +class GuildCard( + val entries: List +) + +class FileListEntry( + val size: Int, + val checksum: Int, + val offset: Int, + val filename: String, +) + +enum class BbCharSelectStatus { + Update, Select, Nonexistent +} diff --git a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt index b35d90ef..eb30d7eb 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/messages/PcMessages.kt @@ -130,7 +130,7 @@ sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_ class Unknown(buffer: Buffer) : PcMessage(buffer) companion object { - protected fun buf( + private fun buf( code: Int, bodySize: Int = 0, writeBody: WritableCursor.() -> Unit = {}, 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 index ad01e80f..0aa23c22 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountState.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/account/AccountState.kt @@ -5,10 +5,7 @@ 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.BbAuthenticationStatus -import world.phantasmal.psoserv.messages.BbMessage -import world.phantasmal.psoserv.messages.FileListEntry -import world.phantasmal.psoserv.messages.PsoCharacter +import world.phantasmal.psoserv.messages.* import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.ServerState import world.phantasmal.psoserv.servers.ServerStateContext @@ -18,6 +15,10 @@ 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) : @@ -27,80 +28,105 @@ sealed class AccountState(ctx: AccountContext) : 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.AuthenticationResponse( - BbAuthenticationStatus.Success, - message.guildCard, - message.teamId, + BbMessage.AuthData( + BbAuthStatus.Success, + ctx.guildCard, + ctx.teamId, + ctx.slot, + ctx.charSelected, ) ) - GetAccount(ctx) + Account(ctx) } else { unexpectedMessage(message) } } - class GetAccount(ctx: AccountContext) : AccountState(ctx) { - override fun process(message: BbMessage): AccountState = - if (message is BbMessage.GetAccount) { - // TODO: Send correct guild card number and team ID. - ctx.send(BbMessage.Account(0, 0)) - - GetCharacters(ctx) - } else { - unexpectedMessage(message) - } - } - - class GetCharacters(ctx: AccountContext) : AccountState(ctx) { + class Account(ctx: AccountContext) : AccountState(ctx) { override fun process(message: BbMessage): AccountState = when (message) { - is BbMessage.CharacterSelect -> { - // TODO: Look up character data. - ctx.send( - BbMessage.CharacterSelectResponse( - 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, + 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.ChecksumResponse(true)) + ctx.send(BbMessage.ChecksumAck(true)) - GetGuildCardData(ctx) + GuildCardData(ctx) } + is BbMessage.Disconnect -> Final(ctx) + else -> unexpectedMessage(message) } } - class GetGuildCardData(ctx: AccountContext) : AccountState(ctx) { + class GuildCardData(ctx: AccountContext) : AccountState(ctx) { private val guildCardBuffer = Buffer.withSize(54672) override fun process(message: BbMessage): AccountState = @@ -134,7 +160,7 @@ sealed class AccountState(ctx: AccountContext) : this } else { - GetFiles(ctx) + DownloadFiles(ctx) } } @@ -146,7 +172,7 @@ sealed class AccountState(ctx: AccountContext) : } } - class GetFiles(ctx: AccountContext) : AccountState(ctx) { + class DownloadFiles(ctx: AccountContext) : AccountState(ctx) { private var fileChunkNo = 0 override fun process(message: BbMessage): AccountState = 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 index bfe4c03d..162b22fc 100644 --- a/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthState.kt +++ b/psoserv/src/main/kotlin/world/phantasmal/psoserv/servers/auth/AuthState.kt @@ -1,7 +1,7 @@ package world.phantasmal.psoserv.servers.auth import mu.KLogger -import world.phantasmal.psoserv.messages.BbAuthenticationStatus +import world.phantasmal.psoserv.messages.BbAuthStatus import world.phantasmal.psoserv.messages.BbMessage import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.ServerState @@ -23,10 +23,12 @@ sealed class AuthState(ctx: AuthContext) : if (message is BbMessage.Authenticate) { // TODO: Actual authentication. ctx.send( - BbMessage.AuthenticationResponse( - BbAuthenticationStatus.Success, + BbMessage.AuthData( + BbAuthStatus.Success, message.guildCard, message.teamId, + slot = 0, + selected = false, ) ) ctx.send(