Block server now sends player to a lobby.

This commit is contained in:
Daan Vanden Bosch 2021-08-06 17:01:51 +02:00
parent 21e9ebaaee
commit e6abad4f09
15 changed files with 335 additions and 155 deletions

View File

@ -51,7 +51,7 @@ expect class Buffer {
fun getFloat(offset: Int): Float
/**
* Reads a ASCII-encoded string at the given offset.
* Reads an ASCII-encoded string at the given offset.
*/
fun getStringAscii(offset: Int, maxByteLength: Int, nullTerminated: Boolean): String
@ -100,6 +100,18 @@ expect class Buffer {
*/
fun setFloat(offset: Int, value: Float): Buffer
/**
* Writes an ASCII-encoded string at the given offset. If [str] is shorter than [byteLength],
* nul bytes will be inserted until [byteLength] bytes have been written.
*/
fun setStringAscii(offset: Int, str: String, byteLength: Int): Buffer
/**
* Writes a UTF-16-encoded string at the given offset. If less than [byteLength] bytes can be
* written this way, nul bytes will be inserted until [byteLength] bytes have been written.
*/
fun setStringUtf16(offset: Int, str: String, byteLength: Int): Buffer
/**
* Writes 0 bytes to the entire buffer.
*/

View File

@ -6,6 +6,7 @@ import org.khronos.webgl.Int8Array
import org.khronos.webgl.Uint8Array
import org.w3c.dom.WindowOrWorkerGlobalScope
import world.phantasmal.psolib.Endianness
import kotlin.math.min
external val self: WindowOrWorkerGlobalScope
@ -156,6 +157,36 @@ actual class Buffer private constructor(
return this
}
actual fun setStringAscii(offset: Int, str: String, byteLength: Int): Buffer {
checkOffset(offset, byteLength)
for (i in 0 until min(str.length, byteLength)) {
val codePoint = str[i].code.toByte()
dataView.setInt8(offset + i, codePoint)
}
for (i in str.length until byteLength) {
dataView.setInt8(offset + i, 0)
}
return this
}
actual fun setStringUtf16(offset: Int, str: String, byteLength: Int): Buffer {
checkOffset(offset, byteLength)
for (i in 0 until min(str.length, byteLength / 2)) {
val codePoint = str[i].code.toShort()
dataView.setInt16(offset + 2 * i, codePoint)
}
for (i in 2 * str.length until byteLength) {
dataView.setInt8(offset + i, 0)
}
return this
}
actual fun zero(): Buffer =
fillByte(0)

View File

@ -4,6 +4,7 @@ import world.phantasmal.psolib.Endianness
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.*
import kotlin.math.min
actual class Buffer private constructor(
private var buf: ByteBuffer,
@ -157,6 +158,36 @@ actual class Buffer private constructor(
return this
}
actual fun setStringAscii(offset: Int, str: String, byteLength: Int): Buffer {
checkOffset(offset, byteLength)
for (i in 0 until min(str.length, byteLength)) {
val codePoint = str[i].code.toByte()
buf.put(offset + i, codePoint)
}
for (i in str.length until byteLength) {
buf.put(offset + i, 0)
}
return this
}
actual fun setStringUtf16(offset: Int, str: String, byteLength: Int): Buffer {
checkOffset(offset, byteLength)
for (i in 0 until min(str.length, byteLength / 2)) {
val codePoint = str[i].code.toShort()
buf.putShort(offset + 2 * i, codePoint)
}
for (i in 2 * str.length until byteLength) {
buf.putShort(offset + i, 0)
}
return this
}
actual fun zero(): Buffer =
fillByte(0)

View File

@ -221,7 +221,7 @@ private fun initialize(config: Config): PhantasmalServer {
)
}
for (block in blocks.values) {
for ((index, block) in blocks.values.withIndex()) {
LOGGER.info {
"""Configuring block server ${block.name} ("${block.uiName}") to bind to ${block.bindPair}."""
}
@ -230,7 +230,7 @@ private fun initialize(config: Config): PhantasmalServer {
BlockServer(
block.name,
block.bindPair,
block.uiName,
blockNo = index + 1,
)
)
}

View File

@ -11,6 +11,8 @@ private const val KEY_SIZE: Int = 48
const val BB_HEADER_SIZE: Int = 8
const val BB_MSG_SIZE_POS: Int = 0
const val BB_MSG_CODE_POS: Int = 2
const val BB_MSG_FLAGS_POS: Int = 4
const val BB_MSG_BODY_POS: Int = 8
object BbMessageDescriptor : MessageDescriptor<BbMessage> {
override val headerSize: Int = BB_HEADER_SIZE
@ -18,8 +20,8 @@ object BbMessageDescriptor : MessageDescriptor<BbMessage> {
override fun readHeader(buffer: Buffer): Header {
val size = buffer.getUShort(BB_MSG_SIZE_POS).toInt()
val code = buffer.getUShort(BB_MSG_CODE_POS).toInt()
// Ignore 4 flag bytes.
return Header(code, size)
val flags = buffer.getInt(BB_MSG_FLAGS_POS)
return Header(code, size, flags)
}
override fun readMessage(buffer: Buffer): BbMessage =
@ -30,8 +32,11 @@ object BbMessageDescriptor : MessageDescriptor<BbMessage> {
0x0007 -> BbMessage.BlockList(buffer)
0x0010 -> BbMessage.MenuSelect(buffer)
0x0019 -> BbMessage.Redirect(buffer)
0x0061 -> BbMessage.CharData(buffer)
0x0067 -> BbMessage.JoinLobby(buffer)
0x0083 -> BbMessage.LobbyList(buffer)
0x0093 -> BbMessage.Authenticate(buffer)
0x0095 -> BbMessage.GetCharData(buffer)
0x00A0 -> BbMessage.ShipList(buffer)
0x01DC -> BbMessage.GuildCardHeader(buffer)
0x02DC -> BbMessage.GuildCardChunk(buffer)
@ -40,8 +45,9 @@ object BbMessageDescriptor : MessageDescriptor<BbMessage> {
0x00E2 -> BbMessage.Account(buffer)
0x00E3 -> BbMessage.CharSelect(buffer)
0x00E4 -> BbMessage.CharSelectAck(buffer)
0x00E5 -> BbMessage.CharData(buffer)
0x00E5 -> BbMessage.Char(buffer)
0x00E6 -> BbMessage.AuthData(buffer)
0x00E7 -> BbMessage.FullCharacterData(buffer)
0x01E8 -> BbMessage.Checksum(buffer)
0x02E8 -> BbMessage.ChecksumAck(buffer)
0x03E8 -> BbMessage.GetGuildCardHeader(buffer)
@ -56,6 +62,7 @@ object BbMessageDescriptor : MessageDescriptor<BbMessage> {
sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_SIZE) {
override val code: Int get() = buffer.getUShort(BB_MSG_CODE_POS).toInt()
override val size: Int get() = buffer.getUShort(BB_MSG_SIZE_POS).toInt()
override val flags: Int get() = buffer.getInt(BB_MSG_FLAGS_POS)
// 0x0003
class InitEncryption(buffer: Buffer) : BbMessage(buffer), InitEncryptionMessage {
@ -126,20 +133,16 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
require(value.size == 4)
setByteArray(0, value)
}
override var port: Int
get() = uShort(4).toInt()
set(value) {
require(value in 0..65535)
setShort(4, value.toShort())
}
override var port: UShort
get() = uShort(4)
set(value) = setUShort(4, value)
constructor(ipAddress: ByteArray, port: Int) : this(
constructor(ipAddress: ByteArray, port: UShort) : this(
buf(0x0019, 8) {
require(ipAddress.size == 4)
require(port in 0..65535)
writeByteArray(ipAddress)
writeShort(port.toShort())
writeUShort(port)
writeShort(0) // Padding.
}
)
@ -151,6 +154,55 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
)
}
// 0x0061
class CharData(buffer: Buffer) : BbMessage(buffer)
// 0x0067
class JoinLobby(buffer: Buffer) : BbMessage(buffer) {
val playerCount: Int get() = flags
var clientId: UByte
get() = uByte(0)
set(value) = setUByte(0, value)
var leaderId: UByte
get() = uByte(1)
set(value) = setUByte(1, value)
var lobbyNo: UByte
get() = uByte(3)
set(value) = setUByte(3, value)
var blockNo: UShort
get() = uShort(4)
set(value) = setUShort(4, value)
constructor(
clientId: UByte,
leaderId: UByte,
disableUdp: Boolean,
lobbyNo: UByte,
blockNo: UShort,
event: UShort,
) : this(
buf(0x0067, 12 + 1312, flags = 1) { // TODO: Set flags to player count.
writeUByte(clientId)
writeUByte(leaderId)
writeByte(if (disableUdp) 1 else 0)
writeUByte(lobbyNo)
writeUShort(blockNo)
writeUShort(event)
writeInt(0) // Unused.
repeat(328) { writeInt(0) }
}
)
override fun toString(): String =
messageString(
"playerCount" to playerCount,
"clientId" to clientId,
"leaderId" to leaderId,
"lobbyNo" to lobbyNo,
"blockNo" to blockNo,
)
}
// 0x0083
class LobbyList(buffer: Buffer) : BbMessage(buffer) {
constructor() : this(
@ -174,17 +226,15 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
val guildCard: Int get() = int(4)
val version: Short get() = short(8)
val teamId: Int get() = int(16)
val userName: String
get() = stringAscii(offset = 20, maxByteLength = 16, nullTerminated = true)
val password: String
get() = stringAscii(offset = 68, maxByteLength = 16, nullTerminated = true)
val userName: String get() = stringAscii(offset = 20, maxByteLength = 16)
val password: String get() = stringAscii(offset = 68, maxByteLength = 16)
val magic: Int get() = int(132) // Should be 0xDEADBEEF
val charSlot: Int get() = byte(136).toInt()
val charSelected: Boolean get() = byte(137).toInt() != 0
}
// 0x0095
class GetCharacterInfo(buffer: Buffer) : BbMessage(buffer) {
class GetCharData(buffer: Buffer) : BbMessage(buffer) {
constructor() : this(buf(0x0095))
}
@ -259,7 +309,8 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
buf(0x00E2, 2804) {
// 276 Bytes of unknown data.
repeat(69) { writeInt(0) }
writeByteArray(DEFAULT_KEYBOARD_GAMEPAD_CONFIG)
writeByteArray(DEFAULT_KEYBOARD_CONFIG)
writeByteArray(DEFAULT_GAMEPAD_CONFIG)
writeInt(guildCard)
writeInt(teamId)
// 2092 Bytes of team data.
@ -297,7 +348,7 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
}
// 0x00E5
class CharData(buffer: Buffer) : BbMessage(buffer) {
class Char(buffer: Buffer) : BbMessage(buffer) {
constructor(char: PsoCharacter) : this(
buf(0x00E5, 128) {
writeInt(char.slot)
@ -363,6 +414,26 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
)
}
// 0x00E7
class FullCharacterData(buffer: Buffer) : BbMessage(buffer) {
constructor(char: PsoCharData) : this(
buf(0x00E7, 14744) {
repeat(211) { writeInt(0) }
repeat(3) { writeShort(0) } // ATP/MST/EVP
writeShort(char.hp)
repeat(3) { writeShort(0) } // DFP/ATA/LCK
writeShort(0) // Unknown.
repeat(2) { writeInt(0) } // Unknown.
writeInt(char.level)
writeInt(char.exp)
repeat(2835) { writeInt(0) }
writeByteArray(DEFAULT_KEYBOARD_CONFIG)
writeByteArray(DEFAULT_GAMEPAD_CONFIG)
repeat(527) { writeInt(0) }
}
)
}
// 0x01E8
class Checksum(buffer: Buffer) : BbMessage(buffer) {
constructor(checksum: Int) : this(
@ -430,21 +501,21 @@ sealed class BbMessage(override val buffer: Buffer) : AbstractMessage(BB_HEADER_
code: Int,
bodySize: Int = 0,
flags: Int = 0,
writeBody: WritableCursor.() -> Unit = {},
writeBody: (WritableCursor.() -> Unit)? = null,
): Buffer {
val size = BB_HEADER_SIZE + bodySize
val buffer = Buffer.withSize(size)
.setShort(BB_MSG_SIZE_POS, size.toShort())
.setShort(BB_MSG_CODE_POS, code.toShort())
.setInt(BB_MSG_FLAGS_POS, flags)
val cursor = buffer.cursor()
// Write header.
.writeShort(size.toShort())
.writeShort(code.toShort())
.writeInt(flags)
if (writeBody != null) {
val cursor = buffer.cursor(BB_MSG_BODY_POS)
cursor.writeBody()
cursor.writeBody()
require(cursor.position == buffer.size) {
"Message buffer should be filled completely, only ${cursor.position} / ${buffer.size} bytes written."
require(cursor.position == bodySize) {
"Message buffer should be filled completely, only ${cursor.position} / $bodySize bytes written."
}
}
return buffer
@ -533,3 +604,9 @@ enum class MenuType(private val type: Int) {
}
}
}
class PsoCharData(
val hp: Short,
val level: Int,
val exp: Int,
)

View File

@ -1,6 +1,6 @@
package world.phantasmal.psoserv.messages
val DEFAULT_KEYBOARD_GAMEPAD_CONFIG: ByteArray = ubyteArrayOf(
val DEFAULT_KEYBOARD_CONFIG: ByteArray = ubyteArrayOf(
0x00u, 0x00u, 0x00u, 0x00u, 0x26u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
0x00u, 0x00u, 0x22u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
0x10u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x13u, 0x00u,
@ -37,10 +37,14 @@ val DEFAULT_KEYBOARD_GAMEPAD_CONFIG: ByteArray = ubyteArrayOf(
0x00u, 0x00u, 0x30u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
0x31u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x32u, 0x00u,
0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x33u, 0x00u, 0x00u, 0x00u,
0x01u, 0x00u, 0x00u, 0x00u, 0x00u, 0x01u, 0xffu, 0xffu, 0x00u, 0x00u,
0x01u, 0x00u, 0x00u, 0x00u, 0x02u, 0x00u, 0x00u, 0x00u, 0x04u, 0x00u,
0x00u, 0x00u, 0x08u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u, 0x04u, 0x00u,
0x00u, 0x00u, 0x02u, 0x00u, 0x00u, 0x00u, 0x08u, 0x00u, 0x00u, 0x00u,
0x00u, 0x02u, 0x00u, 0x00u, 0x20u, 0x00u, 0x00u, 0x00u, 0x80u, 0x00u,
0x00u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u,
0x01u, 0x00u, 0x00u, 0x00u,
).asByteArray()
val DEFAULT_GAMEPAD_CONFIG: ByteArray = ubyteArrayOf(
0x00u, 0x01u, 0xffu, 0xffu, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u,
0x02u, 0x00u, 0x00u, 0x00u, 0x04u, 0x00u, 0x00u, 0x00u, 0x08u, 0x00u,
0x01u, 0x00u, 0x00u, 0x00u, 0x04u, 0x00u, 0x00u, 0x00u, 0x02u, 0x00u,
0x00u, 0x00u, 0x08u, 0x00u, 0x00u, 0x00u, 0x00u, 0x02u, 0x00u, 0x00u,
0x20u, 0x00u, 0x00u, 0x00u, 0x80u, 0x00u, 0x00u, 0x00u, 0x00u, 0x01u,
0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u,
).asByteArray()

View File

@ -22,7 +22,7 @@ fun messageString(
append("]")
}
data class Header(val code: Int, val size: Int)
data class Header(val code: Int, val size: Int, val flags: Int)
interface Message {
val buffer: Buffer
@ -30,6 +30,7 @@ interface Message {
val size: Int
val headerSize: Int
val bodySize: Int get() = size - headerSize
val flags: Int
}
interface MessageDescriptor<out MessageType : Message> {
@ -47,7 +48,7 @@ interface InitEncryptionMessage : Message {
interface RedirectMessage : Message {
var ipAddress: ByteArray
var port: Int
var port: UShort
}
abstract class AbstractMessage(override val headerSize: Int) : Message {
@ -60,8 +61,16 @@ abstract class AbstractMessage(override val headerSize: Int) : Message {
protected fun short(offset: Int) = buffer.getShort(headerSize + offset)
protected fun int(offset: Int) = buffer.getInt(headerSize + offset)
protected fun byteArray(offset: Int, size: Int) = ByteArray(size) { byte(offset + it) }
protected fun stringAscii(offset: Int, maxByteLength: Int, nullTerminated: Boolean) =
buffer.getStringAscii(headerSize + offset, maxByteLength, nullTerminated)
protected fun stringAscii(offset: Int, maxByteLength: Int) =
buffer.getStringAscii(headerSize + offset, maxByteLength, nullTerminated = true)
protected fun setUByte(offset: Int, value: UByte) {
buffer.setUByte(headerSize + offset, value)
}
protected fun setUShort(offset: Int, value: UShort) {
buffer.setUShort(headerSize + offset, value)
}
protected fun setByte(offset: Int, value: Byte) {
buffer.setByte(headerSize + offset, value)
@ -77,6 +86,10 @@ abstract class AbstractMessage(override val headerSize: Int) : Message {
}
}
protected fun setStringAscii(offset: Int, str: String, byteLength: Int) {
buffer.setStringAscii(headerSize + offset, str, byteLength)
}
protected fun messageString(vararg props: Pair<String, Any>): String =
messageString(code, size, this::class.simpleName, *props)
}

View File

@ -12,6 +12,8 @@ private const val KEY_SIZE: Int = 4
const val PC_HEADER_SIZE: Int = 4
const val PC_MSG_SIZE_POS: Int = 0
const val PC_MSG_CODE_POS: Int = 2
const val PC_MSG_FLAGS_POS: Int = 3
const val PC_MSG_BODY_POS: Int = 4
object PcMessageDescriptor : MessageDescriptor<PcMessage> {
override val headerSize: Int = PC_HEADER_SIZE
@ -19,8 +21,8 @@ object PcMessageDescriptor : MessageDescriptor<PcMessage> {
override fun readHeader(buffer: Buffer): Header {
val size = buffer.getUShort(PC_MSG_SIZE_POS).toInt()
val code = buffer.getUByte(PC_MSG_CODE_POS).toInt()
// Ignore flag byte at position 3.
return Header(code, size)
val flags = buffer.getUByte(PC_MSG_FLAGS_POS).toInt()
return Header(code, size, flags)
}
override fun readMessage(buffer: Buffer): PcMessage =
@ -40,6 +42,7 @@ object PcMessageDescriptor : MessageDescriptor<PcMessage> {
sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_SIZE) {
override val code: Int get() = buffer.getUByte(PC_MSG_CODE_POS).toInt()
override val size: Int get() = buffer.getUShort(PC_MSG_SIZE_POS).toInt()
override val flags: Int get() = buffer.getUByte(PC_MSG_FLAGS_POS).toInt()
// 0x02
class InitEncryption(buffer: Buffer) : PcMessage(buffer), InitEncryptionMessage {
@ -76,14 +79,14 @@ sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_
constructor() : this(buf(0x0D))
}
// 0x10
class PatchListOk(buffer: Buffer) : PcMessage(buffer)
// 0x12
class PatchDone(buffer: Buffer) : PcMessage(buffer) {
constructor() : this(buf(0x12))
}
// 0x10
class PatchListOk(buffer: Buffer) : PcMessage(buffer)
// 0x13
class WelcomeMessage(buffer: Buffer) : PcMessage(buffer) {
constructor(message: String) : this(
@ -101,24 +104,22 @@ sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_
require(value.size == 4)
setByteArray(0, value)
}
override var port: Int
override var port: UShort
get() {
buffer.endianness = Endianness.Big
val p = uShort(4).toInt()
val p = uShort(4)
buffer.endianness = Endianness.Little
return p
}
set(value) {
require(value in 0..65535)
buffer.endianness = Endianness.Big
setShort(4, value.toShort())
buffer.endianness = Endianness.Little
}
constructor(ipAddress: ByteArray, port: Int) : this(
constructor(ipAddress: ByteArray, port: UShort) : this(
buf(0x14, 8) {
require(ipAddress.size == 4)
require(port in 0..65535)
writeByteArray(ipAddress)
endianness = Endianness.Big
@ -141,21 +142,22 @@ sealed class PcMessage(override val buffer: Buffer) : AbstractMessage(PC_HEADER_
private fun buf(
code: Int,
bodySize: Int = 0,
writeBody: WritableCursor.() -> Unit = {},
writeBody: (WritableCursor.() -> Unit)? = null,
): Buffer {
val size = PC_HEADER_SIZE + bodySize
val buffer = Buffer.withSize(size)
// Write header.
.setShort(PC_MSG_SIZE_POS, size.toShort())
.setByte(PC_MSG_CODE_POS, code.toByte())
.setByte(PC_MSG_FLAGS_POS, 0) // Flags
val cursor = buffer.cursor()
// Write Header
.writeShort(size.toShort())
.writeByte(code.toByte())
.writeByte(0) // Flags
if (writeBody != null) {
val cursor = buffer.cursor(PC_MSG_BODY_POS)
cursor.writeBody()
cursor.writeBody()
require(cursor.position == buffer.size) {
"Message buffer should be filled completely, only ${cursor.position} / ${buffer.size} bytes written."
require(cursor.position == bodySize) {
"Message buffer should be filled completely, only ${cursor.position} / $bodySize bytes written."
}
}
return buffer

View File

@ -19,7 +19,7 @@ class AccountServer(
override fun createCipher() = BbCipher()
override fun createClientReceiver(
sender: ClientSender<BbMessage>,
ctx: ClientContext<BbMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): ClientReceiver<BbMessage> = object : ClientReceiver<BbMessage> {
@ -31,7 +31,7 @@ class AccountServer(
private var charSelected: Boolean = false
init {
sender.send(
ctx.send(
BbMessage.InitEncryption(
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
serverCipher.key,
@ -46,7 +46,7 @@ class AccountServer(
// TODO: Actual authentication.
guildCard = message.guildCard
teamId = message.teamId
send(
ctx.send(
BbMessage.AuthData(
AuthStatus.Success,
guildCard,
@ -56,10 +56,10 @@ class AccountServer(
)
)
// When the player has selected a character, we send him the list of ships to choose
// from.
// When the player has selected a character, we send him the list of ships to
// choose from.
if (message.charSelected) {
send(BbMessage.ShipList(ships.map { it.uiName }))
ctx.send(BbMessage.ShipList(ships.map { it.uiName }))
}
true
@ -67,7 +67,7 @@ class AccountServer(
is BbMessage.GetAccount -> {
// TODO: Send correct guild card number and team ID.
send(BbMessage.Account(0, 0))
ctx.send(BbMessage.Account(0, 0))
true
}
@ -79,7 +79,7 @@ class AccountServer(
if (slot in 0..3) {
slot = message.slot
charSelected = true
send(
ctx.send(
BbMessage.AuthData(
AuthStatus.Success,
guildCard,
@ -88,19 +88,19 @@ class AccountServer(
charSelected,
)
)
send(
ctx.send(
BbMessage.CharSelectAck(slot, CharSelectStatus.Select)
)
} else {
send(
ctx.send(
BbMessage.CharSelectAck(slot, CharSelectStatus.Nonexistent)
)
}
} else {
// Player is previewing characters.
// TODO: Look up character data.
send(
BbMessage.CharData(
ctx.send(
BbMessage.Char(
PsoCharacter(
slot = message.slot,
exp = 0,
@ -133,13 +133,13 @@ class AccountServer(
is BbMessage.Checksum -> {
// TODO: Checksum checking.
send(BbMessage.ChecksumAck(true))
ctx.send(BbMessage.ChecksumAck(true))
true
}
is BbMessage.GetGuildCardHeader -> {
send(
ctx.send(
BbMessage.GuildCardHeader(
guildCardBuffer.size,
crc32Checksum(guildCardBuffer),
@ -158,7 +158,7 @@ class AccountServer(
)
val size = (guildCardBuffer.size - offset).coerceAtMost(MAX_CHUNK_SIZE)
send(
ctx.send(
BbMessage.GuildCardChunk(
message.chunkNo,
guildCardBuffer.cursor(offset, size),
@ -172,7 +172,7 @@ class AccountServer(
is BbMessage.GetFileList -> {
fileChunkNo = 0
send(BbMessage.FileList(FILE_LIST))
ctx.send(BbMessage.FileList(FILE_LIST))
true
}
@ -185,7 +185,7 @@ class AccountServer(
MAX_CHUNK_SIZE
)
send(BbMessage.FileChunk(fileChunkNo, FILE_BUFFER.cursor(offset, size)))
ctx.send(BbMessage.FileChunk(fileChunkNo, FILE_BUFFER.cursor(offset, size)))
if (offset + size < FILE_BUFFER.size) {
fileChunkNo++
@ -197,7 +197,12 @@ class AccountServer(
is BbMessage.MenuSelect -> {
if (message.menuType == MenuType.Ship) {
ships.getOrNull(message.itemId - 1)?.let { ship ->
send(BbMessage.Redirect(ship.bindPair.address.address, ship.bindPair.port))
ctx.send(
BbMessage.Redirect(
ship.bindPair.address.address,
ship.bindPair.port.toUShort(),
)
)
}
// Disconnect.
@ -209,11 +214,7 @@ class AccountServer(
is BbMessage.Disconnect -> false
else -> unexpectedMessage(message)
}
private fun send(message: BbMessage) {
sender.send(message)
else -> ctx.unexpectedMessage(message)
}
}

View File

@ -18,12 +18,12 @@ class AuthServer(
override fun createCipher() = BbCipher()
override fun createClientReceiver(
sender: ClientSender<BbMessage>,
ctx: ClientContext<BbMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): ClientReceiver<BbMessage> = object : ClientReceiver<BbMessage> {
init {
sender.send(
ctx.send(
BbMessage.InitEncryption(
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
serverCipher.key,
@ -36,7 +36,7 @@ class AuthServer(
override fun process(message: BbMessage): Boolean = when (message) {
is BbMessage.Authenticate -> {
// TODO: Actual authentication.
send(
ctx.send(
BbMessage.AuthData(
AuthStatus.Success,
message.guildCard,
@ -45,19 +45,15 @@ class AuthServer(
selected = false,
)
)
send(
BbMessage.Redirect(accountServerAddress.address, accountServerPort)
ctx.send(
BbMessage.Redirect(accountServerAddress.address, accountServerPort.toUShort())
)
// Disconnect.
false
}
else -> unexpectedMessage(message)
}
private fun send(message: BbMessage) {
sender.send(message)
else -> ctx.unexpectedMessage(message)
}
}
}

View File

@ -5,11 +5,12 @@ import world.phantasmal.psoserv.encryption.Cipher
import world.phantasmal.psoserv.messages.AuthStatus
import world.phantasmal.psoserv.messages.BbMessage
import world.phantasmal.psoserv.messages.BbMessageDescriptor
import world.phantasmal.psoserv.messages.PsoCharData
class BlockServer(
name: String,
bindPair: Inet4Pair,
private val uiName: String,
private val blockNo: Int,
) : GameServer<BbMessage>(name, bindPair) {
override val messageDescriptor = BbMessageDescriptor
@ -17,12 +18,12 @@ class BlockServer(
override fun createCipher() = BbCipher()
override fun createClientReceiver(
sender: ClientSender<BbMessage>,
ctx: ClientContext<BbMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): ClientReceiver<BbMessage> = object : ClientReceiver<BbMessage> {
init {
sender.send(
ctx.send(
BbMessage.InitEncryption(
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
serverCipher.key,
@ -35,7 +36,7 @@ class BlockServer(
override fun process(message: BbMessage): Boolean = when (message) {
is BbMessage.Authenticate -> {
// TODO: Actual authentication.
send(
ctx.send(
BbMessage.AuthData(
AuthStatus.Success,
message.guildCard,
@ -44,18 +45,37 @@ class BlockServer(
message.charSelected,
)
)
send(BbMessage.LobbyList())
// TODO: Send 0x00E7
send(BbMessage.GetCharacterInfo())
ctx.send(BbMessage.LobbyList())
ctx.send(
BbMessage.FullCharacterData(
PsoCharData(
hp = 20,
level = 0,
exp = 0,
)
)
)
ctx.send(BbMessage.GetCharData())
true
}
else -> unexpectedMessage(message)
}
is BbMessage.CharData -> {
ctx.send(
BbMessage.JoinLobby(
clientId = 0u,
leaderId = 0u,
disableUdp = true,
lobbyNo = 0u,
blockNo = blockNo.toUShort(),
event = 0u,
)
)
private fun send(message: BbMessage) {
sender.send(message)
true
}
else -> ctx.unexpectedMessage(message)
}
}
}

View File

@ -1,18 +1,11 @@
package world.phantasmal.psoserv.servers
import mu.KLogger
import world.phantasmal.psoserv.encryption.Cipher
import world.phantasmal.psoserv.messages.Message
import world.phantasmal.psoserv.messages.MessageDescriptor
import java.net.Socket
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,
@ -33,14 +26,27 @@ abstract class GameServer<MessageType : Message>(
protected abstract fun createCipher(): Cipher
protected abstract fun createClientReceiver(
sender: ClientSender<MessageType>,
ctx: ClientContext<MessageType>,
serverCipher: Cipher,
clientCipher: Cipher,
): ClientReceiver<MessageType>
protected fun unexpectedMessage(message: MessageType): Boolean {
logger.debug { "Unexpected message: $message." }
return true
protected interface ClientReceiver<MessageType : Message> {
fun process(message: MessageType): Boolean
}
protected class ClientContext<MessageType : Message>(
private val logger: KLogger,
private val handler: SocketHandler<MessageType>,
) {
fun send(message: MessageType, encrypt: Boolean = true) {
handler.sendMessage(message, encrypt)
}
fun unexpectedMessage(message: MessageType): Boolean {
logger.debug { "Unexpected message: $message." }
return true
}
}
private inner class GameClientHandler(client: String, socket: Socket) :
@ -49,16 +55,8 @@ abstract class GameServer<MessageType : Message>(
private val serverCipher = createCipher()
private val clientCipher = createCipher()
private val handler: ClientReceiver<MessageType> =
createClientReceiver(
object : ClientSender<MessageType> {
override fun send(message: MessageType, encrypt: Boolean) {
sendMessage(message, encrypt)
}
},
serverCipher,
clientCipher,
)
private val clientContext = ClientContext(logger, this)
private val receiver = createClientReceiver(clientContext, serverCipher, clientCipher)
override val messageDescriptor = this@GameServer.messageDescriptor
@ -67,7 +65,7 @@ abstract class GameServer<MessageType : Message>(
override val writeEncryptCipher: Cipher = serverCipher
override fun processMessage(message: MessageType): ProcessResult =
if (handler.process(message)) {
if (receiver.process(message)) {
ProcessResult.Ok
} else {
// Give the client some time to disconnect.

View File

@ -15,12 +15,12 @@ class PatchServer(
override fun createCipher() = PcCipher()
override fun createClientReceiver(
sender: ClientSender<PcMessage>,
ctx: ClientContext<PcMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): ClientReceiver<PcMessage> = object : ClientReceiver<PcMessage> {
init {
sender.send(
ctx.send(
PcMessage.InitEncryption(
"Patch Server. Copyright SonicTeam, LTD. 2001",
serverCipher.key,
@ -32,31 +32,27 @@ class PatchServer(
override fun process(message: PcMessage): Boolean = when (message) {
is PcMessage.InitEncryption -> {
send(PcMessage.Login())
ctx.send(PcMessage.Login())
true
}
is PcMessage.Login -> {
send(PcMessage.WelcomeMessage(welcomeMessage))
send(PcMessage.PatchListStart())
send(PcMessage.PatchListEnd())
ctx.send(PcMessage.WelcomeMessage(welcomeMessage))
ctx.send(PcMessage.PatchListStart())
ctx.send(PcMessage.PatchListEnd())
true
}
is PcMessage.PatchListOk -> {
send(PcMessage.PatchDone())
ctx.send(PcMessage.PatchDone())
// Disconnect.
false
}
else -> unexpectedMessage(message)
}
private fun send(message: PcMessage) {
sender.send(message)
else -> ctx.unexpectedMessage(message)
}
}
}

View File

@ -66,7 +66,7 @@ class ProxyServer(
}
is RedirectMessage -> {
val oldAddress = Inet4Pair(message.ipAddress, message.port)
val oldAddress = Inet4Pair(message.ipAddress, message.port.toInt())
redirectMap[oldAddress]?.let { newAddress ->
logger.debug {
@ -74,7 +74,7 @@ class ProxyServer(
}
message.ipAddress = newAddress.address.address
message.port = newAddress.port
message.port = newAddress.port.toUShort()
return ProcessResult.Changed
}

View File

@ -19,12 +19,12 @@ class ShipServer(
override fun createCipher() = BbCipher()
override fun createClientReceiver(
sender: ClientSender<BbMessage>,
ctx: ClientContext<BbMessage>,
serverCipher: Cipher,
clientCipher: Cipher,
): ClientReceiver<BbMessage> = object : ClientReceiver<BbMessage> {
init {
sender.send(
ctx.send(
BbMessage.InitEncryption(
"Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.",
serverCipher.key,
@ -37,7 +37,7 @@ class ShipServer(
override fun process(message: BbMessage): Boolean = when (message) {
is BbMessage.Authenticate -> {
// TODO: Actual authentication.
send(
ctx.send(
BbMessage.AuthData(
AuthStatus.Success,
message.guildCard,
@ -46,7 +46,7 @@ class ShipServer(
message.charSelected,
)
)
send(
ctx.send(
BbMessage.BlockList(uiName, blocks.map { it.uiName })
)
@ -56,8 +56,11 @@ class ShipServer(
is BbMessage.MenuSelect -> {
if (message.menuType == MenuType.Block) {
blocks.getOrNull(message.itemId - 1)?.let { block ->
send(
BbMessage.Redirect(block.bindPair.address.address, block.bindPair.port)
ctx.send(
BbMessage.Redirect(
block.bindPair.address.address,
block.bindPair.port.toUShort(),
)
)
}
@ -68,11 +71,7 @@ class ShipServer(
}
}
else -> unexpectedMessage(message)
}
private fun send(message: BbMessage) {
sender.send(message)
else -> ctx.unexpectedMessage(message)
}
}
}