diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index ab91891b..24bce829 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -38,7 +38,9 @@ // 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. +// 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. struct ClientConfig { uint64_t magic; uint8_t bb_game_state; @@ -47,19 +49,20 @@ struct ClientConfig { uint32_t proxy_destination_address; uint16_t proxy_destination_port; parray unused; -} __attribute__((packed)); +}; struct ClientConfigBB { ClientConfig cfg; parray unused; -} __attribute__((packed)); +}; // 00: Invalid command // 01 (S->C): Lobby message box -// Message box appears in lower-right corner; user must press a key to continue +// A small message box appears in lower-right corner, and the player must press +// a key to continue. struct SC_TextHeader_01_06_11_B0 { le_uint32_t unused; @@ -68,7 +71,7 @@ struct SC_TextHeader_01_06_11_B0 { }; // 02 (S->C): Start encryption (except on BB) -// Client will respond with an (encrypted) 9A, 9D, or 9E command +// Client will respond with an (encrypted) 9A, 9D, or 9E command. struct S_ServerInit_DC_PC_GC_02_17 { ptext copyright; @@ -87,7 +90,7 @@ struct S_ServerInit_Patch_02 { }; // 03 (S->C): Start encryption (BB) -// Client will respond with a 93 command +// Client will respond with a 93 command. struct S_ServerInit_BB_03 { ptext copyright; @@ -100,7 +103,11 @@ struct S_ServerInit_BB_03 { // 04 (S->C): Set guild card number and update client config ("security data") // Client will respond with a 96 command, but only the first time it receives // this command - for later 04 commands, the client will still update its client -// config but will not respond. +// config but will not respond. Changing the security data at any time seems ok, +// but changing the guild card number of a client after it's initially set can +// confuse the client, and (on pre-V3 clients) possibly corrupt the character +// data. For this reason, newserv tries pretty hard to hide the remote guild +// card number when clients connect to the proxy server. struct S_UpdateClientConfig_DC_PC_GC_04 { le_uint32_t player_tag; @@ -126,10 +133,9 @@ struct C_Login_Patch_04 { // No arguments // 06: Chat -// Server->client format is same as 01 command +// Server->client format is same as 01 command. // Client->server format is very similar; we include a zero-length array in this -// struct to make parsing easier -// The guild_card_number field is used only in server->client commands +// struct to make parsing easier. struct C_Chat_06 { uint64_t unused; @@ -159,7 +165,7 @@ struct S_MenuEntry_DC_GC_07 : S_MenuEntry { }; // No arguments // 08 (S->C): Game list -// Client responds with 09 and 10 commands +// Client responds with 09 and 10 commands (or nothing if the player cancels). // 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. @@ -183,7 +189,7 @@ struct S_CheckDirectory_Patch_09 { }; // 09 (C->S): Menu item info request -// Server will respond with an 11 command, or an A3 if it's the quest menu +// Server will respond with an 11 command, or an A3 if it's the quest menu. struct C_MenuItemInfoRequest_09 { le_uint32_t menu_id; @@ -207,7 +213,7 @@ struct C_MenuItemInfoRequest_09 { struct C_MenuSelection { le_uint32_t menu_id; le_uint32_t item_id; - // Password is only present when client attempts to join a locked game + // Password is only present when client attempts to join a locked game. union { char dcgc[0]; char16_t pcbb[0]; @@ -215,22 +221,25 @@ struct C_MenuSelection { }; // 11: Ship info -// Same format as 01 command +// Same format as 01 command. // 12: Session complete (patch server) // No arguments // 13 (S->C): Message box (patch server) -// Same as 1A/D5 command +// Same as 1A/D5 command. // 13 (C->S): Confirm file write -// Client sends this in response to each 13 sent by the server -// Format not documented here +// Client sends this in response to each 13 sent by the server. It appears these +// are only sent by V3 and BB - PSO PC does not send these. +// TODO: Document format here (even though newserv ignores these) // 13 (S->C): Write online quest file -// Used for downloading online quests +// Used for downloading online quests. All chunks except the last must have +// 0x400 data bytes. When downloading an online quest, the .bin and .dat chunks +// may be interleaved (although newserv currently sends them sequentially). -// Header flag = file chunk index +// Header flag = file chunk index (start offset / 0x400) struct S_WriteFile_13_A7 { ptext filename; uint8_t data[0x400]; @@ -242,8 +251,9 @@ struct S_WriteFile_13_A7 { // 16: Invalid command // 17 (S->C): Start encryption at login server (except on BB) -// Same format as 02 command, but a different copyright string -// Client will respond with a DB command +// Same format as 02 command, but a different copyright string. +// Client will respond with a DB command if on V3; otherwise it will respond +// with a 9D. // 18: Invalid command @@ -252,7 +262,8 @@ struct S_WriteFile_13_A7 { // Because PSO PC and some versions of PSO GC use the same port but different // protocols, we use a specially-crafted 19 command to send them to two -// different ports depending on the client version. +// different ports depending on the client version. I originally saw this +// technique used by Schthack; I don't know if it was his original creation. struct S_Reconnect_19 { be_uint32_t address; @@ -276,13 +287,17 @@ struct S_ReconnectSplit_19 { // Client will usually respond with a D6 command (see D6 for more information) // Contents are plain text (char on DC/GC, char16_t on PC/BB). There must be at // least one null character ('\0') before the end of the command data. +// There is a bug in V3 (and possibly all versions) where if this command is +// sent after the client has joined a lobby, the chat log window contents will +// appear in the message box, prepended to the message text from the command. // 1B: Invalid command // 1C: Invalid command // 1D: Ping // No arguments -// When sent to the client, the client will respond with a 1D command +// When sent to the client, the client will respond with a 1D command. Data sent +// by the server is ignored; the client always sends a 1D command with no data. // 1E: Invalid command @@ -332,7 +347,9 @@ struct SC_GameCardCheck_BB_22 { // 3E: Invalid command // 3F: Invalid command -// 40: Guild card search +// 40 (C->S): Guild card search +// Server should respond with a 41 command if the target is online. If the +// target is not online, server doesn't respond at all. struct C_GuildCardSearch_40 { le_uint32_t player_tag; @@ -340,6 +357,8 @@ struct C_GuildCardSearch_40 { le_uint32_t target_guild_card_number; }; +// 41 (S->C): Guild card search result + template struct S_GuildCardSearchResult { le_uint32_t player_tag; @@ -357,16 +376,16 @@ struct S_GuildCardSearchResult_PC_41 : S_GuildCardSearchResult { }; struct S_GuildCardSearchResult_BB_41 : S_GuildCardSearchResult { }; -// 41: Invalid command // 42: Invalid command // 43: Invalid command // 44 (C->S): Confirm open file -// The client sends a 44 to confirm each 44 sent by the server -// FOrmat not documented here +// Client sends this in response to each 44 sent by the server. +// TODO: Are these V3-only just like the 13 (C->S) command? +// TODO: Document format here (even though newserv ignores these) // 44 (S->C): Open file for download -// Used for downloading online quests +// Used for downloading online quests. struct S_OpenFile_PC_GC_44_A6 { ptext name; @@ -413,31 +432,32 @@ struct S_OpenFile_BB_44_A6 { // 5F: Invalid command // 60: Broadcast command -// When client sends this command, the server should forward it to all players -// in the same game/lobby -// See ReceiveSubcommands for details on contents +// When a client sends this command, the server should forward it to all players +// in the same game/lobby, except the player who originally sent the command. +// See ReceiveSubcommands for details on contents. -// 61: Player data +// 61 (C->S): Player data // See PSOPlayerDataPC, PSOPlayerDataGC, PSOPlayerDataBB in Player.hh for this -// command's format +// command's format. // 62: Target command -// When client sends this command, the server should forward it to the player -// identified by header.flag in the same game/lobby -// See ReceiveSubcommands for details on contents +// When a client sends this command, the server should forward it to the player +// identified by header.flag in the same game/lobby, even if that player is the +// player who originally sent it. +// See ReceiveSubcommands for details on contents. // 63: Invalid command // 64 (S->C): Join game -// This is sent to the joining player; the other players get a 65 instead +// This is sent to the joining player; the other players get a 65 instead. // Header flag = entry count template struct S_JoinGame { parray variations; // Unlike lobby join commands, these are filled in in their slot positions. - // That is, if there's one player in a game with ID 2, then the first two of - // these are blank and the player's data is in the third entry here. + // That is, if there's only one player in a game with ID 2, then the first two + // of these are blank and the player's data is in the third entry here. LobbyDataT lobby_data[4]; uint8_t client_id; uint8_t leader_id; @@ -456,9 +476,9 @@ struct S_JoinGame { PlayerInventory inventory; DispDataT disp; }; - // This field is only present if the game (+client) is Episode 3. Similarly to - // lobby_data above, all four of these are always present and they are filled - // in in slot positions. + // This field is only present if the game (and client) is Episode 3. Similarly + // to lobby_data above, all four of these are always present and they are + // filled in in slot positions. PlayerEntry players_ep3[4]; }; struct S_JoinGame_PC_64 : S_JoinGame { }; @@ -495,7 +515,7 @@ struct S_JoinLobby_GC_65_67_68 : S_JoinLobby { }; // 66 (S->C): Other player left game -// Not sent to the leaving player +// This is sent to all players in a game except the leaving player. // Header flag = leaving player ID (same as client_id); struct S_LeaveLobby_66_69 { @@ -505,32 +525,30 @@ struct S_LeaveLobby_66_69 { }; // 67 (S->C): Join lobby -// This is sent to the joining player; the other players get a 68 instead -// Same format as 65 command +// This is sent to the joining player; the other players get a 68 instead. +// Same format as 65 command, but used for lobbies instead of games. // 68 (S->C): Other player joined lobby -// Same format as 65 command +// Same format as 65 command, but used for lobbies instead of games. // 69 (S->C): Other player left lobby -// Not sent to the leaving player -// Same format as 66 command +// Same format as 66 command, but used for lobbies instead of games. // 6A: Invalid command // 6B: Invalid command // 6C: Broadcast command -// Same format and usage as 60 command +// Same format and usage as 60 command. // 6D: Target command -// Same format and usage as 62 command +// Same format and usage as 62 command. // 6E: Invalid command // 6F: Set game status // This command is sent when a player is done loading and other players can then -// join the game -// On BB, this command is sent as 016F if a quest is in progress and the game -// should not be joined by anyone else +// join the game. On BB, this command is sent as 016F if a quest is in progress +// and the game should not be joined by anyone else. // 70: Invalid command // 71: Invalid command @@ -551,9 +569,13 @@ struct S_LeaveLobby_66_69 { // 80: Invalid command // 81: Simple mail -// Format is the same in both directions. On GC (and probably other versions -// too) the unused space after the text contains uninitialized memory when the -// client sends this command +// Format is the same in both directions. The server should forward the command +// to the player with to_guild_gard_number, if they are online. +// On GC (and probably other versions too) the unused space after the text +// contains uninitialized memory when the client sends this command. None of the +// unused space appears to contain anything important; newserv clears the +// uninitialized data for security reasons before forwarding and things seem to +// work fine. struct SC_SimpleMail_GC_81 { le_uint32_t player_tag; @@ -566,9 +588,14 @@ struct SC_SimpleMail_GC_81 { // 82: Invalid command // 83 (S->C): Lobby menu -// This sets the menu item IDs that the client uses for the lobby teleport menu +// This sets the menu item IDs that the client uses for the lobby teleport menu. +// The client expects 15 items here (or 20 on Episode 3); sending more or fewer +// items does not change the lobby count on the client. If fewer entries are +// sent, the menu item IDs for some lobbies will not be set, and the client will +// likely send 84 commands that don't make sense if the player chooses one of +// lobbies with unset IDs. -// Command is a list of these; header.flag is the entry count (15, or 20 on Ep3) +// Command is a list of these; header.flag is the entry count (15 or 20) struct S_LobbyListEntry_83 { le_uint32_t menu_id; le_uint32_t item_id; @@ -586,9 +613,11 @@ struct C_LobbySelection_84 { // 86: Invalid command // 87: Invalid command -// 88 (S->C): Lobby arrows +// 88 (S->C): Update lobby arrows -// Command is a list of these; header.flag is the entry count +// Command is a list of these; header.flag is the entry count. There should +// be an update for every player in the lobby in this command, even if their +// arrow color isn't being changed. struct S_ArrowUpdateEntry_88 { le_uint32_t player_tag; le_uint32_t guild_card_number; @@ -596,15 +625,15 @@ struct S_ArrowUpdateEntry_88 { }; // 89 (C->S): Set lobby arrow -// Header flag = arrow color number; no other arguments -// Server should send an 88 command to all players in the lobby +// header.flag = arrow color number; no other arguments. +// Server should send an 88 command to all players in the lobby. // 8A (C->S): Request lobby/game name // No arguments // 8A (S->C): Lobby/game name // Contents is a string (char16_t on PC/BB, char on DC/GC) containing the lobby -// or game name +// or game name. // 8B: Invalid command // 8C: Invalid command @@ -630,11 +659,18 @@ struct C_Login_BB_93 { // 95 (S->C): Request player data // No arguments +// For some reason, some servers send high values in the header.flag field here. +// From what I can tell, that field appears to be completely unused by the +// client - sending zero works just fine. The original Sega servers had some +// uninitialized memory bugs, of which that may have been one, and other private +// servers may have just duplicated Sega's behavior verbatim. // Client will respond with a 61 command // 96 (C->S): Client checksum struct C_ClientChecksum_GC_96 { + // It seems only at most 48 bits of this are actually used - the highest-order + // (last) two bytes seem to always be zero. le_uint64_t checksum; }; @@ -642,25 +678,21 @@ struct C_ClientChecksum_GC_96 { // No arguments // 98 (C->S): Leave game -// Same format a 61 command -// Client will send an 84 when it's ready to join a lobby +// Same format a 61 command. +// Client will send an 84 when it's ready to join a lobby. // 99 (C->S): Server time accepted // No arguments // 9A (S->C): License verification result -// The result code is sent in the header.flag field. -// 00 = license ok (don't save to memory card) +// The result code is sent in the header.flag field. Result codes: +// 00 = license ok (don't save to memory card; client responds with 9E command) // 01 = registration required (client responds with a 9C command) -// 02 = license ok (save to memory card) +// 02 = license ok (save to memory card; client responds with 9E command) // 03 = access key invalid (125) // 04 = serial number invalid (126) -// 05 = network error (119) -// 06 = network error (119) // 07 = invalid Hunter's License (117) // 08 = Hunter's License expired (116) -// 09 = network error (119) -// 0A = network error (119) // 0B = HL not registered under this serial number/access key (112) // 0C = HL not registered under this serial number/access key (113) // 0D = HL not registered under this serial number/access key (114) @@ -670,7 +702,6 @@ struct C_ClientChecksum_GC_96 { // 11 = Hunter's License expired (116) // 12 = invalid Hunter's License (117) // 13 = servers under maintenance (118) -// 14 = network error (119) // Seems like most (all?) of the rest of the codes are "network error" (119). // 9A (C->S): Initial login (no password or client config) @@ -689,7 +720,7 @@ struct C_Login_DC_PC_GC_9A { // 9B: Invalid command -// 9C (C->S): Log in result +// 9C (C->S): Login result // The only possible error here seems to be wrong password (127) which is // displayed if the header.flag field is zero. If header.flag is nonzero, the // client proceeds with the login procedure by sending a 9E. @@ -706,6 +737,9 @@ struct C_Register_DC_PC_GC_9C { }; // 9D (C->S): Log in +// In some cases the client sends extra unused data at the end of this command. +// This seems to only occur if the client has not yet received an 04 (guild card +// number / client config) command. struct C_Login_PC_9D { le_uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04) @@ -724,8 +758,10 @@ struct C_LoginWithUnusedSpace_PC_9D : C_Login_PC_9D { }; // 9E (C->S): Log in with client config +// In some cases the client sends extra unused data at the end of this command. +// This seems to only occur if the client has not yet received an 04 (guild card +// number / client config) command. -// This struct is identical to PC's 9D command, but it has more data at the end struct C_Login_GC_9E : C_Login_PC_9D { union ClientConfigFields { ClientConfig cfg; @@ -743,18 +779,22 @@ struct C_LoginWithUnusedSpace_GC_9E : C_Login_GC_9E { // No arguments // A0 (S->C): Ship select menu -// Same as 07 command +// Same as 07 command. // A1 (C->S): Change block // No arguments // A1 (S->C): Block select menu -// Same as 07 command +// Same as 07 command. -// A2 (C->S): Request quest list +// A2 (C->S): Request quest menu // No arguments // A2 (S->C): Quest menu +// Client will respond with an 09, 10, or A9 command. For 09, the server should +// send the category or quest description via an A3 response; for 10, the server +// should send another quest menu (if a category was chosen), or send the quest +// data with 44/13 commands; for A9, the server does not need to respond. template struct S_QuestMenuEntry { @@ -778,20 +818,23 @@ struct S_QuestMenuEntry_BB_A2_A4 { // Same format as 1A/D5 command (plain text) // A4 (S->C): Download quest menu -// Same format as A2 +// Same format as A2, but can be used when not in a game. The client responds +// similarly to A2; the primary difference is that if a quest is chosen, it +// should be sent with A6/A7 commands rather than 44/13, and it must be in a +// different encrypted format (not described here). // A5: Invalid command // A6: Open file for download -// Used for download quests and GBA games -// Same format as 44 +// Used for download quests and GBA games. +// Same format as 44. // A7: Write download file -// Same format as 13 +// Same format as 13. // A8: Invalid command -// A9: Quest menu closed +// A9: Quest menu closed (canceled) // No arguments // AA: Invalid command @@ -802,32 +845,33 @@ struct S_QuestMenuEntry_BB_A2_A4 { // No arguments // When all players in a game have sent an AC to the server, the server should // send them all an AC back, which starts the quest for all players at -// (approximately) the same time +// (approximately) the same time. // Sending this command to a GC client when it is not waiting to start a quest -// will cause it to crash +// will cause it to crash. // AD: Invalid command // AE: Invalid command // AF: Invalid command // B0: Text message -// Same format as 01 command (plain text with unused header) +// Same format as 01 command. // B1 (C->S): Request server time // No arguments // B1 (S->C): Server time -// Contents is a string of format "%Y:%m:%d: %H:%M:%S.000" +// Contents is a string like "%Y:%m:%d: %H:%M:%S.000" (the space is not a typo). // For example: 2022:03:30: 15:36:42.000 +// Client will respond with a 99 command. // B2 (S->C): Write memory -// GC v1.0 and v1.1 only -// Format unknown -// Client will respond with a B3 command +// GC v1.0 and v1.1 only. +// Format unknown. +// Client will respond with a B3 command. // B3 (C->S): Write memory response -// GC v1.0 and v1.1 only -// Format unknown +// GC v1.0 and v1.1 only. +// Format unknown. // B4: Invalid command // B5: Invalid command @@ -843,9 +887,11 @@ struct S_RankUpdate_GC_Ep3_B7 { le_uint32_t jukebox_songs_unlocked; }; -// B8 (S->C): Send card definitions (Episode 3) +// B8 (S->C): Update card definitions (Episode 3) // Contents is a single little-endian le_uint32_t specifying the size of the -// (PRS-compressed) data, followed immediately by the data +// (PRS-compressed) data, followed immediately by the data. newserv sends the +// system/ep3/cardupdate.mnr file verbatim using this command when an Episode 3 +// client connects to the login server. // B9: Invalid command @@ -871,14 +917,15 @@ struct S_Meseta_GC_Ep3_BA { // C0 (C->S): Request choice search options // No arguments +// Server should respond with a C0 command (described below). // C0 (S->C): Choice search options -// Command is a list of these; header.flag is the entry count (incl. top-level) +// Command is a list of these; header.flag is the entry count (incl. top-level). template struct S_ChoiceSearchEntry { - // category_ids are nonzero; if the high byte is nonzero then the category can - // be set by the user at any time; otherwise it can't. + // Category IDs are nonzero; if the high byte of the ID is nonzero then the + // category can be set by the user at any time; otherwise it can't. le_uint16_t parent_category_id; // 0 for top-level categories le_uint16_t category_id; ptext text; @@ -906,12 +953,12 @@ struct C_CreateGame { le_uint64_t unused; ptext name; ptext password; - uint8_t difficulty; - uint8_t battle_mode; + uint8_t difficulty; // 0-3 + uint8_t battle_mode; // 0 or 1 // Note: Episode 3 uses the challenge mode flag for view battle permissions. // 0 = view battle allowed; 1 = not allowed - uint8_t challenge_mode; - uint8_t episode; // unused on DC/PC + uint8_t challenge_mode; // 0 or 1 + uint8_t episode; // 1-4 on V3+; unused on DC/PC }; struct C_CreateGame_DC_GC_C1_EC : C_CreateGame { }; struct C_CreateGame_PC_C1 : C_CreateGame { }; @@ -922,6 +969,7 @@ struct C_CreateGame_BB_C1 : C_CreateGame { }; // C2 (C->S): Set choice search parameters +// Server does not respond. struct C_SetChoiceSearchParameters_C2 { le_uint16_t disabled; // 0 = enabled, 1 = disabled @@ -934,6 +982,7 @@ struct C_SetChoiceSearchParameters_C2 { }; // C3 (C->S): Execute choice search +// Server should respond with a C4 command. struct C_ExecuteChoiceSearch_C3 { le_uint32_t unknown; @@ -966,7 +1015,7 @@ struct S_ChoiceSearchResultEntry_GC_C4 { }; // C5 (S->C): Challenge rank update -// TODO: Document format for this command +// TODO: Document format and usage for this command // C6 (C->S): Set blocked senders list @@ -975,17 +1024,18 @@ struct C_SetBlockedSenders_C6 { }; // C7 (C->S): Enable simple mail auto-reply -// Same format as 1A/D5 command (plain text) +// Same format as 1A/D5 command (plain text). // C8 (C->S): Disable simple mail auto-reply // C9: Broadcast command (Episode 3) -// Same as 60, but only send to Episode 3 clients +// Same as 60, but only send to Episode 3 clients. // CA (C->S): Ep3 server data request +// TODO: Document this. It does many different things. // CB: Broadcast command (Episode 3) -// Same as 60, but only send to Episode 3 clients +// Same as 60, but only send to Episode 3 clients. // CC: Invalid command // CD: Invalid command @@ -994,7 +1044,7 @@ struct C_SetBlockedSenders_C6 { // D0 (C->S): Execute trade via trade window // General sequence: client sends D0, server sends D1 to that client, server -// sends D3 to other client, server sends D4 to both (?) clients +// sends D3 to other client, server sends D4 to both (?) clients. // Format unknown // D1 (S->C): Confirm trade to initiator @@ -1003,30 +1053,37 @@ struct C_SetBlockedSenders_C6 { // D2: Invalid command // D3 (S->C): Execute trade with accepter -// Format unknown; appears to be same as D0 +// Format unknown; appears to be same as D0. // D4 (S->C): Close trade // No arguments // D5: Large message box -// Same as 1A command +// Same as 1A command. // D6 (C->S): Large message box closed (GC) // No arguments // DC and PC do not send this command at all. GC v1.0 and v1.1 will send this // command when any large message box is closed; GC Plus and Episode 3 will send // this only for large message boxes that are sent before the client has joined -// a lobby. +// a lobby. (After joining a lobby, large message boxes will still be displayed +// if sent by the server, but the client won't send a D6 when they are closed.) // D7 (C->S): Request GBA game file +// The server should send the requested file using A6/A7 commands. struct C_GBAGameRequest_GC_D7 { ptext filename; }; +// D8 (C->S): Info board request +// No arguments +// The server should respond with a D8 command (described below). + // D8 (S->C): Info board -// Command is a list of these; header.flag is the entry count +// Command is a list of these; header.flag is the entry count. There should be +// one entry for each player in the current lobby/game. template struct S_InfoBoardEntry_D8 { ptext name; @@ -1036,12 +1093,13 @@ struct S_InfoBoardEntry_PC_BB_D8 : S_InfoBoardEntry_D8 { }; struct S_InfoBoardEntry_DC_GC_D8 : S_InfoBoardEntry_D8 { }; // D9 (C->S): Write info board -// Contents are plain text, like 1A/D5 +// Contents are plain text, like 1A/D5. // DA (S->C): Change lobby event -// Header flag = new event number. No other arguments +// header.flag = new event number; no other arguments. -// DB (S->C): Verify license (GC) +// DB (C->S): Verify license (GC) +// Server should respond with a 9A command. struct C_VerifyLicense_GC_DB { ptext unused; @@ -1055,7 +1113,8 @@ struct C_VerifyLicense_GC_DB { }; // DC: Player menu state (Episode 3) -// No arguments. Client expects server to respond with command = DC, flag = 0 +// No arguments. It seems the client expects the server to respond with another +// DC command with header.flag = 0. // DC: Guild card data (BB) @@ -1119,6 +1178,8 @@ struct S_PlayerPreview_BB_E3 { }; // E4: CARD lobby game (Episode 3) +// When client sends an E4, server should respond with another E4 (but these +// commands have different formats). // Header flag = seated state (1 = present, 0 = leaving) struct C_CardLobbyGame_GC_E4 { @@ -1155,7 +1216,7 @@ struct C_CreateCharacter_BB_E5 { }; // E6: Spectator team list (Episode 3) -// Same format as 08 command +// Same format as 08 command. // E6 (S->C): Set guild card number and update client config (BB) @@ -1169,7 +1230,7 @@ struct S_ClientInit_BB_E6 { }; // E7: Save or load full player data -// See export_bb_player_data() in Player.cc for format +// See export_bb_player_data() in Player.cc for format. // E8 (C->S): Client checksum (BB) // E8 (S->C): Accept client checksum (BB) @@ -1198,7 +1259,7 @@ struct S_StreamFileChunk_BB_02EB { }; // EC: Create game (Episode 3) -// Same format as C1; some fields are unused (e.g. episode, difficulty) +// Same format as C1; some fields are unused (e.g. episode, difficulty). // EC: Leave character select (BB) @@ -1701,7 +1762,9 @@ struct G_BoxItemDropRequest_6xA2 { // B2: Unknown // B3: Unknown // B4: Unknown +// B5: Episode 3 game setup menu state sync // B5: BB shop request +// B6: Episode 3 map list (server->client only) // B6: BB shop contents (server->client only)