rewrite client config; use BB state from login command

This commit is contained in:
Martin Michelsen
2023-10-30 13:15:41 -07:00
parent 47198779b7
commit 5e2e38f1b5
29 changed files with 1139 additions and 1192 deletions
+57 -80
View File
@@ -88,44 +88,6 @@
// - - $r: Right arrow
// - - $u: Up arrow
// This is the format of newserv's security data, which we call the client
// config. This data is opaque to the client, so this structure is not
// technically part of the PSO protocol. Because it is opaque to the client, we
// can use the server's native-endian types instead of being explicit as we do
// for all the other structs in this file.
enum ClientStateBB : uint8_t {
// Initial connection; server will redirect client to another port
INITIAL_LOGIN = 0x00,
// Second connection; server will send client game data and account data
DOWNLOAD_DATA = 0x01,
// Third connection; client will show the choose character menu
CHOOSE_PLAYER = 0x02,
// Fourth connection; used for saving characters only. If you do not create a
// character, the server sets this state during the third connection so this
// connection is effectively skipped.
SAVE_PLAYER = 0x03,
// Fifth connection; redirects client to login server
SHIP_SELECT = 0x04,
// All other connections
IN_GAME = 0x05,
};
struct ClientConfig {
uint64_t magic = 0;
uint32_t flags = 0;
uint32_t specific_version = 0;
uint32_t proxy_destination_address = 0;
uint16_t proxy_destination_port = 0;
parray<uint8_t, 0x0A> unused;
} __packed__;
struct ClientConfigBB {
ClientConfig cfg;
uint8_t bb_game_state = 0;
uint8_t bb_player_index = 0;
parray<uint8_t, 0x06> unused;
} __packed__;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// PATCH SERVER COMMANDS ///////////////////////////////////////////////////////
@@ -478,8 +440,7 @@ struct C_LegacyLogin_BB_04 {
// config set by the E6 command (and returned in the 93 command). In most cases,
// E6 should be used for BB clients instead of 04.
template <typename ClientConfigT>
struct S_UpdateClientConfig {
struct S_UpdateClientConfig_DC_PC_04 {
// Note: What we call player_tag here is actually three fields: two uint8_ts
// followed by a le_uint16_t. It's unknown what the uint8_t fields are for
// (they seem to always be zero), but the le_uint16_t is likely a boolean
@@ -489,16 +450,20 @@ struct S_UpdateClientConfig {
// player is present and zero when none is present.
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
// The ClientConfig structure describes how newserv uses this command; other
// servers do not use the same format for the following 0x20 or 0x28 bytes (or
// may not use it at all). The cfg field is opaque to the client; it will send
// back the contents verbatim in its next 9E command (or on request via 9F).
ClientConfigT cfg;
} __packed__;
struct S_UpdateClientConfig_DC_PC_V3_04 : S_UpdateClientConfig<ClientConfig> {
struct S_UpdateClientConfig_V3_04 {
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
// This field is opaque to the client; it will send back the contents verbatim
// in its next 9E command (or on request via 9F).
parray<uint8_t, 0x20> client_config;
} __packed__;
struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig<ClientConfigBB> {
struct S_UpdateClientConfig_BB_04 {
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
parray<uint8_t, 0x28> client_config;
} __packed__;
// 05: Disconnect
@@ -530,7 +495,7 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig<ClientConfigBB> {
// the chat string accidentally.) We call this byte private_flags in the places
// where newserv uses it.
// 07 (S->C): Ship select menu
// 07 (S->C): Ship or block select menu
// Internal name: RcvDirList
// This command triggers a general form of blocking menu, which was used for
// both the ship select and block select menus by Sega (and all other private
@@ -539,6 +504,8 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig<ClientConfigBB> {
// there was a separate command to send the block list, but it was scrapped.
// Perhaps this was used for command A1, which is identical to 07 and A0 in all
// versions of PSO (except DC NTE).
// The menu is titles "Ship Select" unless the first menu item begins with the
// text "BLOCK" (all caps), in which case it is titled "Block Select".
// Command is a list of these; header.flag is the entry count. The first entry
// is not included in the count and does not appear on the client. The text of
@@ -860,17 +827,20 @@ struct S_ReconnectSplit_19 {
// 21: GameGuard control (old versions of BB)
// Format unknown
// 22: GameGuard check (BB)
// 0022: GameGuard check (BB)
// Command 0022 is a 16-byte challenge (sent in the data field) using the
// following structure.
struct SC_GameCardCheck_BB_0022 {
struct SC_GameGuardCheck_BB_0022 {
parray<le_uint32_t, 4> data;
} __packed__;
// Command 0122 uses a 4-byte challenge sent in the header.flag field instead.
// This version of the command has no other arguments.
// 0122 (C->S): Time deviation (BB)
// This command is sent when the client executes a quest opcode 5D (gettime) and
// the returned timestamp is before the previous timestamp returned, but not by
// too much - it seems the game only considers deltas between 3 seconds and 30
// minutes suspicious for these purposes.
// 23 (S->C): Momoka Item Exchange result (BB)
// Sent in response to a 6xD9 command from the client.
@@ -1693,7 +1663,18 @@ struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 {
struct C_Login_BB_93 {
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
pstring<TextEncoding::ASCII, 0x08> unused;
le_uint32_t sub_version = 0;
uint8_t language;
uint8_t character_slot;
// Values for connection_phase:
// 00 - initial connection (client will request system file, characters, etc.)
// 01 - choose character
// 02 - create character
// 03 - apply updates from dressing room
// 04 - login server
// 05 - lobby server (and beyond)
uint8_t connection_phase;
uint8_t client_code;
le_uint32_t team_id = 0;
pstring<TextEncoding::ASCII, 0x30> username;
pstring<TextEncoding::ASCII, 0x30> password;
@@ -1711,16 +1692,10 @@ struct C_Login_BB_93 {
// the client config starts 8 bytes earlier on those versions and the entire
// command is 8 bytes shorter, hence this odd-looking union.
union VariableLengthSection {
union ClientConfigFields {
ClientConfigBB cfg;
pstring<TextEncoding::ASCII, 0x28> version_string;
parray<le_uint32_t, 10> as_u32;
} __packed__;
ClientConfigFields old_clients_cfg;
parray<uint8_t, 0x28> old_client_config;
struct NewFormat {
parray<le_uint32_t, 2> hardware_info;
ClientConfigFields cfg;
parray<uint8_t, 0x28> client_config;
} __packed__ new_clients;
} __packed__ var;
} __packed__;
@@ -1909,11 +1884,7 @@ struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D {
// header.flag is 1 if the client has UDP disabled.
struct C_Login_GC_9E : C_Login_DC_PC_GC_9D {
union ClientConfigFields {
ClientConfig cfg;
parray<uint8_t, 0x20> data;
ClientConfigFields() : data() {}
} __packed__ client_config;
parray<uint8_t, 0x20> client_config;
} __packed__;
struct C_LoginExtended_GC_9E : C_Login_GC_9E {
SC_MeetUserExtension<TextEncoding::MARKED> extension;
@@ -1951,10 +1922,16 @@ struct C_LoginExtended_BB_9E {
// 9F (C->S): Client config / security data response (V3/BB)
// The data is opaque to the client, as described at the top of this file.
// If newserv ever sent a 9F command (it currently does not), the response
// format here would be ClientConfig (0x20 bytes) on V3, or ClientConfigBB (0x28
// bytes) on BB. However, on BB, this returns the client config that was set by
// a preceding 04 command, not the config set by a preceding E6 command.
// If newserv ever sent a 9F command (it currently does not). On BB, this
// command does not work during the data server phase.
struct C_ClientConfig_V3_9F {
parray<uint8_t, 0x20> data;
} __packed__;
struct C_ClientConfig_BB_9F {
parray<uint8_t, 0x28> data;
} __packed__;
// A0 (C->S): Change ship
// Internal name: SndShipList
@@ -2806,7 +2783,7 @@ struct S_TournamentList_GC_Ep3_E0 {
parray<Entry, 0x20> entries;
} __packed__;
// E0 (C->S): Request team and key config (BB)
// E0 (C->S): Request system file (BB)
// No arguments. The server should respond with an E1 or E2 command.
// E1 (S->C): Game information (Episode 3)
@@ -2830,10 +2807,10 @@ struct S_GameInformation_GC_Ep3_E1 {
/* 0298 */
} __packed__;
// E1 (S->C): Team and key config missing? (BB)
// E1 (S->C): Create system file (BB)
// This seems to take the place of 00E2 in certain cases. Perhaps it was used
// when a client hadn't logged in before and didn't have a team or key config,
// so the client should use appropriate defaults.
// when a client hadn't logged in before and didn't have a system file, so the
// client should use appropriate defaults.
struct S_TeamAndKeyConfigMissing_00E1_BB {
// If success is not equal to 1, the client shows a message saying "Forced
@@ -3033,16 +3010,16 @@ struct C_JoinSpectatorTeam_GC_Ep3_E6_Flag01 {
// Same format as 08 command.
// E6 (S->C): Set guild card number and update client config (BB)
// BB clients have multiple client configs. This command sets the client config
// that is returned by the 93 commands, but does not affect the client config
// set by the 04 command (and returned in the 9E and 9F commands).
// This command sets the player's guild card number. During the data server
// phase, it also sets the client config and enabled features (these fields are
// ignored during the game server phase).
struct S_ClientInit_BB_00E6 {
le_uint32_t error = 0;
le_uint32_t error_code = 0;
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
le_uint32_t team_id = 0;
ClientConfigBB cfg;
parray<uint8_t, 0x28> client_config;
uint8_t can_create_team = 1;
uint8_t episode_4_unlocked = 1;
parray<uint8_t, 2> unused;
@@ -3325,7 +3302,7 @@ struct C_PromoteTeamMember_BB_11EA {
struct S_TeamMembershipInformation_BB_12EA {
le_uint32_t unknown_a1 = 0; // Command is ignored unless this is 0
le_uint32_t guild_card_number = 0;
le_uint32_t guild_card_number = 0; // Team membership ID?
le_uint32_t team_id = 0;
le_uint32_t unknown_a4 = 0;
le_uint32_t privilege_level = 0;