Character selection on psoserv now works.

This commit is contained in:
Daan Vanden Bosch 2021-08-01 17:49:53 +02:00
parent fccf2255d3
commit f6e0ed06a9
4 changed files with 182 additions and 137 deletions

View File

@ -12,69 +12,6 @@ const val BB_HEADER_SIZE: Int = 8
const val BB_MSG_SIZE_POS: Int = 0 const val BB_MSG_SIZE_POS: Int = 0
const val BB_MSG_CODE_POS: Int = 2 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<GuildCardEntry>
)
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<BbMessage> { object BbMessageDescriptor : MessageDescriptor<BbMessage> {
override val headerSize: Int = BB_HEADER_SIZE override val headerSize: Int = BB_HEADER_SIZE
@ -97,11 +34,12 @@ object BbMessageDescriptor : MessageDescriptor<BbMessage> {
0x03DC -> BbMessage.GetGuildCardChunk(buffer) 0x03DC -> BbMessage.GetGuildCardChunk(buffer)
0x00E0 -> BbMessage.GetAccount(buffer) 0x00E0 -> BbMessage.GetAccount(buffer)
0x00E2 -> BbMessage.Account(buffer) 0x00E2 -> BbMessage.Account(buffer)
0x00E3 -> BbMessage.CharacterSelect(buffer) 0x00E3 -> BbMessage.CharSelect(buffer)
0x00E5 -> BbMessage.CharacterSelectResponse(buffer) 0x00E4 -> BbMessage.CharSelectAck(buffer)
0x00E6 -> BbMessage.AuthenticationResponse(buffer) 0x00E5 -> BbMessage.CharData(buffer)
0x00E6 -> BbMessage.AuthData(buffer)
0x01E8 -> BbMessage.Checksum(buffer) 0x01E8 -> BbMessage.Checksum(buffer)
0x02E8 -> BbMessage.ChecksumResponse(buffer) 0x02E8 -> BbMessage.ChecksumAck(buffer)
0x03E8 -> BbMessage.GetGuildCardHeader(buffer) 0x03E8 -> BbMessage.GetGuildCardHeader(buffer)
0x01EB -> BbMessage.FileList(buffer) 0x01EB -> BbMessage.FileList(buffer)
0x02EB -> BbMessage.FileChunk(buffer) 0x02EB -> BbMessage.FileChunk(buffer)
@ -243,12 +181,30 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
} }
// 0x00E3 // 0x00E3
class CharacterSelect(buffer: Buffer) : BbMessage(buffer) { class CharSelect(buffer: Buffer) : BbMessage(buffer) {
val slot: Int get() = uByte(0).toInt() val slot: Int get() = uByte(0).toInt()
val select: Boolean get() = byte(4).toInt() != 0 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( constructor(char: PsoCharacter) : this(
buf(0x00E5, 128) { buf(0x00E5, 128) {
writeInt(char.slot) writeInt(char.slot)
@ -281,24 +237,33 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
) )
} }
class AuthenticationResponse(buffer: Buffer) : BbMessage(buffer) { class AuthData(buffer: Buffer) : BbMessage(buffer) {
constructor(status: BbAuthenticationStatus, guildCard: Int, teamId: Int) : this( constructor(
status: BbAuthStatus,
guildCard: Int,
teamId: Int,
slot: Int,
selected: Boolean,
) : this(
buf(0x00E6, 60) { buf(0x00E6, 60) {
writeInt( writeInt(
when (status) { when (status) {
BbAuthenticationStatus.Success -> 0 BbAuthStatus.Success -> 0
BbAuthenticationStatus.Error -> 1 BbAuthStatus.Error -> 1
BbAuthenticationStatus.UnknownUser -> 8 BbAuthStatus.Nonexistent -> 8
} }
) )
writeInt(0x10000) writeInt(0x10000)
writeInt(guildCard) writeInt(guildCard)
writeInt(teamId) writeInt(teamId)
writeInt( writeInt(
if (status == BbAuthenticationStatus.Success) (0xDEADBEEF).toInt() else 0 if (status == BbAuthStatus.Success) (0xDEADBEEF).toInt() else 0
) )
// 36 Bytes of unknown data. writeByte(slot.toByte())
repeat(9) { writeInt(0) } writeByte(if (selected) 1 else 0)
// 34 Bytes of unknown data.
writeShort(0)
repeat(8) { writeInt(0) }
writeInt(0x102) writeInt(0x102)
} }
) )
@ -315,7 +280,7 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
val checksum: Int get() = int(0) val checksum: Int get() = int(0)
} }
class ChecksumResponse(buffer: Buffer) : BbMessage(buffer) { class ChecksumAck(buffer: Buffer) : BbMessage(buffer) {
constructor(success: Boolean) : this( constructor(success: Boolean) : this(
buf(0x02E8, 4) { buf(0x02E8, 4) {
writeInt(if (success) 1 else 0) 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) class Unknown(buffer: Buffer) : BbMessage(buffer)
companion object { companion object {
protected fun buf( private fun buf(
code: Int, code: Int,
bodySize: Int = 0, bodySize: Int = 0,
flags: 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<GuildCardEntry>
)
class FileListEntry(
val size: Int,
val checksum: Int,
val offset: Int,
val filename: String,
)
enum class BbCharSelectStatus {
Update, Select, Nonexistent
}

View File

@ -130,7 +130,7 @@ sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_
class Unknown(buffer: Buffer) : PcMessage(buffer) class Unknown(buffer: Buffer) : PcMessage(buffer)
companion object { companion object {
protected fun buf( private fun buf(
code: Int, code: Int,
bodySize: Int = 0, bodySize: Int = 0,
writeBody: WritableCursor.() -> Unit = {}, writeBody: WritableCursor.() -> Unit = {},

View File

@ -5,10 +5,7 @@ import world.phantasmal.core.math.clamp
import world.phantasmal.psolib.Endianness import world.phantasmal.psolib.Endianness
import world.phantasmal.psolib.buffer.Buffer import world.phantasmal.psolib.buffer.Buffer
import world.phantasmal.psolib.cursor.cursor import world.phantasmal.psolib.cursor.cursor
import world.phantasmal.psoserv.messages.BbAuthenticationStatus import world.phantasmal.psoserv.messages.*
import world.phantasmal.psoserv.messages.BbMessage
import world.phantasmal.psoserv.messages.FileListEntry
import world.phantasmal.psoserv.messages.PsoCharacter
import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.FinalServerState
import world.phantasmal.psoserv.servers.ServerState import world.phantasmal.psoserv.servers.ServerState
import world.phantasmal.psoserv.servers.ServerStateContext import world.phantasmal.psoserv.servers.ServerStateContext
@ -18,6 +15,10 @@ import world.phantasmal.psoserv.utils.crc32Checksum
class AccountContext( class AccountContext(
logger: KLogger, logger: KLogger,
socketSender: SocketSender<BbMessage>, socketSender: SocketSender<BbMessage>,
var guildCard: Int = -1,
var teamId: Int = -1,
var slot: Int = 0,
var charSelected: Boolean = false,
) : ServerStateContext<BbMessage>(logger, socketSender) ) : ServerStateContext<BbMessage>(logger, socketSender)
sealed class AccountState(ctx: AccountContext) : sealed class AccountState(ctx: AccountContext) :
@ -27,39 +28,61 @@ sealed class AccountState(ctx: AccountContext) :
override fun process(message: BbMessage): AccountState = override fun process(message: BbMessage): AccountState =
if (message is BbMessage.Authenticate) { if (message is BbMessage.Authenticate) {
// TODO: Actual authentication. // TODO: Actual authentication.
ctx.guildCard = message.guildCard
ctx.teamId = message.teamId
ctx.send( ctx.send(
BbMessage.AuthenticationResponse( BbMessage.AuthData(
BbAuthenticationStatus.Success, BbAuthStatus.Success,
message.guildCard, ctx.guildCard,
message.teamId, ctx.teamId,
ctx.slot,
ctx.charSelected,
) )
) )
GetAccount(ctx) Account(ctx)
} else { } else {
unexpectedMessage(message) unexpectedMessage(message)
} }
} }
class GetAccount(ctx: AccountContext) : AccountState(ctx) { class Account(ctx: AccountContext) : AccountState(ctx) {
override fun process(message: BbMessage): AccountState = override fun process(message: BbMessage): AccountState =
if (message is BbMessage.GetAccount) { when (message) {
is BbMessage.GetAccount -> {
// TODO: Send correct guild card number and team ID. // TODO: Send correct guild card number and team ID.
ctx.send(BbMessage.Account(0, 0)) ctx.send(BbMessage.Account(0, 0))
GetCharacters(ctx) this
} else {
unexpectedMessage(message)
}
} }
class GetCharacters(ctx: AccountContext) : AccountState(ctx) { is BbMessage.CharSelect -> {
override fun process(message: BbMessage): AccountState = if (message.select) {
when (message) { // TODO: Verify slot.
is BbMessage.CharacterSelect -> { 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. // TODO: Look up character data.
ctx.send( ctx.send(
BbMessage.CharacterSelectResponse( BbMessage.CharData(
PsoCharacter( PsoCharacter(
slot = message.slot, slot = message.slot,
exp = 0, exp = 0,
@ -85,22 +108,25 @@ sealed class AccountState(ctx: AccountContext) :
) )
) )
) )
}
this this
} }
is BbMessage.Checksum -> { is BbMessage.Checksum -> {
// TODO: Checksum checking. // 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) else -> unexpectedMessage(message)
} }
} }
class GetGuildCardData(ctx: AccountContext) : AccountState(ctx) { class GuildCardData(ctx: AccountContext) : AccountState(ctx) {
private val guildCardBuffer = Buffer.withSize(54672) private val guildCardBuffer = Buffer.withSize(54672)
override fun process(message: BbMessage): AccountState = override fun process(message: BbMessage): AccountState =
@ -134,7 +160,7 @@ sealed class AccountState(ctx: AccountContext) :
this this
} else { } 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 private var fileChunkNo = 0
override fun process(message: BbMessage): AccountState = override fun process(message: BbMessage): AccountState =

View File

@ -1,7 +1,7 @@
package world.phantasmal.psoserv.servers.auth package world.phantasmal.psoserv.servers.auth
import mu.KLogger 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.messages.BbMessage
import world.phantasmal.psoserv.servers.FinalServerState import world.phantasmal.psoserv.servers.FinalServerState
import world.phantasmal.psoserv.servers.ServerState import world.phantasmal.psoserv.servers.ServerState
@ -23,10 +23,12 @@ sealed class AuthState(ctx: AuthContext) :
if (message is BbMessage.Authenticate) { if (message is BbMessage.Authenticate) {
// TODO: Actual authentication. // TODO: Actual authentication.
ctx.send( ctx.send(
BbMessage.AuthenticationResponse( BbMessage.AuthData(
BbAuthenticationStatus.Success, BbAuthStatus.Success,
message.guildCard, message.guildCard,
message.teamId, message.teamId,
slot = 0,
selected = false,
) )
) )
ctx.send( ctx.send(