#pragma once #include #include "PSOProtocol.hh" #pragma pack(push) #pragma pack(1) // Names are like [S|C|SC]_CommandName_[Versions]_Numbers // S/C denotes who sends the command (S = server, C = client, SC = both) // If versions are not specified, the format is the same for all versions. // Sorted by command number. // 00: Invalid command // 01 (S->C): Lobby message box // Message box appears in lower-right corner; user must press a key to continue struct SC_TextHeader_01_06_11 { le_uint32_t unused; le_uint32_t guild_card_number; }; // 02 (S->C): Start encryption (except on BB) // Client will respond with an (encrypted) 9A, 9D, or 9E command struct S_ServerInit_DC_GC_02_17 { ptext copyright; le_uint32_t server_key; // Key for data sent by server le_uint32_t client_key; // Key for data sent by client ptext after_message; }; struct S_ServerInit_Patch_02 { ptext copyright; le_uint32_t server_key; le_uint32_t client_key; // BB rejects the command if it's not exactly this size, so we can't add the // anti-copyright message like we do in the other server init commands }; // 03 (S->C): Start encryption (BB) // Client will respond with a 93 command struct S_ServerInit_BB_03 { ptext copyright; parray server_key; parray client_key; ptext after_message; }; // 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 (TODO: Is this true? It might instead be that it responds with a // 96 only if encryption was started with a 17 rather than an 02) struct S_UpdateClientConfig_DC_PC_GC_04 { le_uint32_t player_tag; le_uint32_t guild_card_number; // This is how newserv uses this command; other servers do not use the same // format for the following 0x20 bytes (or may not use it at all) ClientConfig cfg; }; // 04 (S->C): Request login information (patch server) (no arguments) // 04 (C->S): Log in (patch server) struct C_Login_Patch_04 { parray unused; ptext username; ptext password; }; // 05: Disconnect // No arguments // 06: Chat // 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 C_Chat_06 { uint64_t unused; union { char dcgc[0]; char16_t pcbb[0]; } text; }; // 07 (S->C): Ship select menu // 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 // the first entry becomes the ship name when the client joins a lobby. template struct S_MenuEntry { le_uint32_t menu_id; le_uint32_t item_id; le_uint16_t flags; // should be 0x0F04 ptext text; }; struct S_MenuEntry_PC_BB_07 : S_MenuEntry { }; struct S_MenuEntry_DC_GC_07 : S_MenuEntry { }; // 08 (C->S): Request game list // No arguments // 08 (S->C): Game list // Client responds with 09 and 10 commands // 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. template struct S_GameMenuEntry { le_uint32_t menu_id; le_uint32_t game_id; uint8_t difficulty_tag; // 0x0A = Ep3; else difficulty + 0x22 (so 0x25 = Ult) uint8_t num_players; ptext name; uint8_t episode; // 40 = Ep1, 41 = Ep2, 43 = Ep4. Ignored on Ep3 uint8_t flags; // 02 = locked, 04 = disabled (BB), 10 = battle, 20 = challenge }; struct S_GameMenuEntry_PC_BB_08 : S_GameMenuEntry { }; struct S_GameMenuEntry_GC_08 : S_GameMenuEntry { }; // 09 (S->C): Check directory (patch server) struct S_CheckDirectory_Patch_09 { ptext name; }; // 09 (C->S): Menu item info request // 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; le_uint32_t item_id; }; // 0A: Done checking directory (patch server) // No arguemnts // 0B: Invalid command // 0C: Create game (DCv1) // Format unknown // 0D: Invalid command // 0E: Invalid command // 0F: Invalid command // 10 (C->S): Menu selection 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 union { char dcgc[0]; char16_t pcbb[0]; } password; }; // 11: Ship info // Same format as 01 command // 12: Session complete (patch server) // No arguments // 13 (S->C): Message box (patch server) // 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 // 13 (S->C): Write online quest file // Used for downloading online quests // Header flag = file chunk index struct S_WriteFile_13_A7 { ptext filename; uint8_t data[0x400]; le_uint32_t data_size; }; // 14: Invalid command // 15: Invalid command // 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 // 18: Invalid command // 19 (S->C): Reconnect to different address // Client will disconnect, and reconnect to the given address/port. // 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. struct S_Reconnect_19 { be_uint32_t address; le_uint16_t port; le_uint16_t unused; }; struct S_ReconnectSplit_19 { be_uint32_t pc_address; le_uint16_t pc_port; parray unused1; uint8_t gc_command; uint8_t gc_flag; le_uint16_t gc_size; be_uint32_t gc_address; le_uint16_t gc_port; parray unused2; }; // 1A: Large message box // 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. // 1B: Invalid command // 1C: Invalid command // 1D: Ping // No arguments // When sent to the client, the client will respond with a 1D command // 1E: Invalid command // 1F (S->C): Information menu // Same format and usage as 07 command // 20: Invalid command // 21: GameGuard control (BB) // Format unknown // 22: GameGuard check (BB) // An older version of BB used a 4-byte challenge in the flag field (and the // command had no payload). The latest version uses this 16-byte challenge. struct SC_GameCardCheck_BB_22 { parray data; }; // 23: Invalid command // 24: Invalid command // 25: Invalid command // 26: Invalid command // 27: Invalid command // 28: Invalid command // 29: Invalid command // 2A: Invalid command // 2B: Invalid command // 2C: Invalid command // 2D: Invalid command // 2E: Invalid command // 2F: Invalid command // 30: Invalid command // 31: Invalid command // 32: Invalid command // 33: Invalid command // 34: Invalid command // 35: Invalid command // 36: Invalid command // 37: Invalid command // 38: Invalid command // 39: Invalid command // 3A: Invalid command // 3B: Invalid command // 3C: Invalid command // 3D: Invalid command // 3E: Invalid command // 3F: Invalid command // 40: Guild card search struct C_GuildCardSearch_40 { le_uint32_t player_tag; le_uint32_t searcher_serial_number; le_uint32_t target_serial_number; } __attribute__((packed)); template struct S_GuildCardSearchResult { le_uint32_t player_tag; le_uint32_t searcher_serial_number; le_uint32_t result_serial_number; HeaderT reconnect_command_header; S_Reconnect_19 reconnect_command; ptext location_string; le_uint32_t menu_id; le_uint32_t lobby_id; ptext unused; ptext name; }; struct S_GuildCardSearchResult_PC_40 : S_GuildCardSearchResult { }; struct S_GuildCardSearchResult_DC_GC_40 : S_GuildCardSearchResult { }; struct S_GuildCardSearchResult_BB_40 : 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 // 44 (S->C): Open file for download // Used for downloading online quests struct S_OpenFile_PC_GC_44_A6 { ptext name; le_uint16_t unused; le_uint16_t flags; ptext filename; le_uint32_t file_size; }; struct S_OpenFile_BB_44_A6 { parray unused; le_uint16_t flags; ptext filename; le_uint32_t file_size; ptext name; }; // 45: Invalid command // 46: Invalid command // 47: Invalid command // 48: Invalid command // 49: Invalid command // 4A: Invalid command // 4B: Invalid command // 4C: Invalid command // 4D: Invalid command // 4E: Invalid command // 4F: Invalid command // 50: Invalid command // 51: Invalid command // 52: Invalid command // 53: Invalid command // 54: Invalid command // 55: Invalid command // 56: Invalid command // 57: Invalid command // 58: Invalid command // 59: Invalid command // 5A: Invalid command // 5B: Invalid command // 5C: Invalid command // 5D: Invalid command // 5E: Invalid command // 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 // 61: Player data // See PSOPlayerDataPC, PSOPlayerDataGC, PSOPlayerDataBB in Player.hh for this // 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 // 63: Invalid command // 64 (S->C): Join game // 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. LobbyDataT lobby_data[4]; uint8_t client_id; uint8_t leader_id; uint8_t disable_udp; uint8_t difficulty; uint8_t battle_mode; uint8_t event; uint8_t section_id; uint8_t challenge_mode; le_uint32_t rare_seed; uint8_t episode; uint8_t unused2; // Should be 1 for PSO PC? uint8_t solo_mode; uint8_t unused3; struct PlayerEntry { PlayerInventory inventory; DispDataT disp; }; // This field is only present if the game (+client) is Episode 3. Similarly to // lobby_data above, these are always present and filled in in slot positions. PlayerEntry players_ep3[4]; }; struct S_JoinGame_PC_64 : S_JoinGame { }; struct S_JoinGame_GC_64 : S_JoinGame { }; struct S_JoinGame_BB_64 : S_JoinGame { }; // 65 (S->C): Other player joined game // Header flag = entry count (always 1 for 65 and 68; up to 0x0C for 67) template struct S_JoinLobby { uint8_t client_id; uint8_t leader_id; uint8_t disable_udp; uint8_t lobby_number; le_uint16_t block_number; le_uint16_t event; le_uint32_t unused; struct Entry { LobbyDataT lobby_data; PlayerInventory inventory; DispDataT disp; }; // Note: not all of these will be filled in and sent if the lobby isn't full // (the command size will be shorter than this struct's size) Entry entries[12]; static inline size_t size(size_t used_entries) { return offsetof(S_JoinLobby, entries) + used_entries * sizeof(Entry); } }; struct S_JoinLobby_PC_65_67_68 : S_JoinLobby { }; struct S_JoinLobby_GC_65_67_68 : S_JoinLobby { }; struct S_JoinLobby_BB_65_67_68 : S_JoinLobby { }; // 66 (S->C): Other player left game // Not sent to the leaving player // Header flag = leaving player ID (same as client_id); struct S_LeaveLobby_66_69 { uint8_t client_id; uint8_t leader_id; le_uint16_t unused; }; // 67 (S->C): Join lobby // This is sent to the joining player; the other players get a 68 instead // Same format as 65 command // 68 (S->C): Other player joined lobby // Same format as 65 command // 69 (S->C): Other player left lobby // Not sent to the leaving player // Same format as 66 command // 6A: Invalid command // 6B: Invalid command // 6C: Broadcast command // Same format and usage as 60 command // 6D: Target 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 // 70: Invalid command // 71: Invalid command // 72: Invalid command // 73: Invalid command // 74: Invalid command // 75: Invalid command // 76: Invalid command // 77: Invalid command // 78: Invalid command // 79: Invalid command // 7A: Invalid command // 7B: Invalid command // 7C: Invalid command // 7D: Invalid command // 7E: Invalid command // 7F: Invalid command // 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 struct SC_SimpleMail_GC_81 { le_uint32_t player_tag; le_uint32_t from_serial_number; ptext from_name; le_uint32_t to_serial_number; ptext text; }; // 82: Invalid command // 83 (S->C): Lobby menu // This sets the menu item IDs that the client uses for the lobby teleport menu // Command is a list of these; header.flag is the entry count (15, or 20 on Ep3) struct S_LobbyListEntry_83 { le_uint32_t menu_id; le_uint32_t item_id; le_uint32_t unused; }; // 84 (C->S): Choose lobby struct C_LobbySelection_84 { le_uint32_t menu_id; le_uint32_t item_id; }; // 85: Invalid command // 86: Invalid command // 87: Invalid command // 88 (S->C): Lobby arrows // Command is a list of these; header.flag is the entry count struct S_ArrowUpdateEntry_88 { le_uint32_t player_tag; le_uint32_t serial_number; le_uint32_t arrow_color; }; // 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 // 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 // 8B: Invalid command // 8C: Invalid command // 8D: Invalid command // 8E: Invalid command // 8F: Invalid command // 90: Invalid command // 91: Invalid command // 92: Invalid command // 93 (C->S): Log in (BB) struct C_Login_BB_93 { ptext unused; ptext username; ptext unused2; ptext password; ptext unused3; ClientConfigBB client_config; }; // 94: Invalid command // 95 (S->C): Request player data // No arguments // Client will respond with a 61 command // 96 (C->S): Client checksum struct C_ClientChecksum_GC_96 { le_uint64_t checksum; }; // 97 (S->C): Save to memory card // 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 // 99 (C->S): Server time accepted // No arguments // 9A (S->C): Verify result // 02 = license ok // TODO: figure out the other codes // 9A (C->S): Initial login (no password or client config) struct C_Login_DC_PC_GC_9A { ptext unused; ptext serial_number; ptext access_key; }; // 9B: Invalid command // 9C (C->S): Log in result // 01 = license ok // TODO: figure out the other codes // 9C (C->S): Register struct C_Register_DC_PC_GC_9C { ptext unused; le_uint32_t sub_version; le_uint32_t unused2; ptext serial_number; ptext access_key; ptext password; }; // 9D (C->S): Log in with client config // Same as 9E? (We treat them identicall in newserv) // 9E (C->S): Log in with client config struct C_Login_PC_GC_9D_9E { le_uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04) le_uint32_t guild_card_number; // FF FF FF FF if not set le_uint64_t unused; le_uint32_t sub_version; parray unused2; // 00 01 00 00 ... (rest is 00) ptext serial_number; ptext access_key; ptext serial_number2; ptext access_key2; ptext name; union ClientConfigFields { ClientConfig cfg; parray data; ClientConfigFields() : cfg() { } } client_config; parray unused4; }; // 9F: Invalid command // A0 (C->S): Change ship // No arguments // A0 (S->C): Ship select menu // Same as 07 command // A1 (C->S): Change block // No arguments // A1 (S->C): Block select menu // Same as 07 command // A2 (C->S): Request quest list // No arguments // A2 (S->C): Quest menu template struct S_QuestMenuEntry { le_uint32_t menu_id; le_uint32_t item_id; ptext name; ptext short_desc; }; struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry { }; struct S_QuestMenuEntry_GC_A2_A4 : S_QuestMenuEntry { }; struct S_QuestMenuEntry_BB_A2_A4 { le_uint32_t menu_id; le_uint32_t item_id; ptext name; // Why is this 10 characters longer than on other versions...? ptext short_desc; }; // A3 (S->C): Quest information // Same format as 1A/D5 command (plain text) // A4 (S->C): Download quest menu // Same format as A2 // A5: Invalid command // A6: Open file for download // Used for download quests and GBA games // Same format as 44 // A7: Write download file // Same format as 13 // A8: Invalid command // A9: Quest menu closed // No arguments // AA: Invalid command // AB: Invalid command // AC (C->S): Ready to start quest // AC (S->C): Start quest // 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 // Sending this command to a GC client when it is not waiting to start a quest // will cause it to crash // AD: Invalid command // AE: Invalid command // AF: Invalid command // B0: Text message // Same format as 1A/D5 command (plain text) // 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" // For example: 2022:03:30: 15:36:42.000 // B2 (S->C): Write memory // 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 // B4: Invalid command // B5: Invalid command // B6: Invalid command // B7: Rank update (Episode 3) struct S_RankUpdate_GC_Ep3_B7 { le_uint32_t rank; ptext rank_text; le_uint32_t meseta; le_uint32_t max_meseta; le_uint32_t jukebox_songs_unlocked; }; // B8 (S->C): Send 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 // B9: Invalid command // BA: Meseta transaction (Episode 3) struct C_Meseta_GC_Ep3_BA { le_uint32_t transaction_num; le_uint32_t value; le_uint32_t unknown_token; }; struct S_Meseta_GC_Ep3_BA { le_uint32_t remaining_meseta; le_uint32_t unknown; le_uint32_t unknown_token; // Should match the token sent by the client }; // BB: Invalid command // BC: Invalid command // BD: Invalid command // BE: Invalid command // BF: Invalid command // C0 (C->S): Request choice search options // No arguments // C0 (S->C): Choice search options // 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. le_uint16_t parent_category_id; // 0 for top-level categories le_uint16_t category_id; ptext text; }; struct S_ChoiceSearchEntry_DC_GC_C0 : S_ChoiceSearchEntry { }; struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { }; // Top-level categories are things like "Level", "Class", etc. // Choices for each top-level category immediately follow the category, so // a reasonable order of items is (for example): // 00 00 11 01 "Preferred difficulty" // 11 01 01 01 "Normal" // 11 01 02 01 "Hard" // 11 01 03 01 "Very Hard" // 11 01 04 01 "Ultimate" // 00 00 22 00 "Character class" // 22 00 01 00 "HUmar" // 22 00 02 00 "HUnewearl" // etc. // C1 (C->S): Create game template struct C_CreateGame { le_uint64_t unused; ptext name; ptext password; uint8_t difficulty; uint8_t battle_mode; uint8_t challenge_mode; uint8_t episode; // unused on DC/PC }; struct C_CreateGame_DC_GC_C1_EC : C_CreateGame { }; struct C_CreateGame_PC_C1 : C_CreateGame { }; struct C_CreateGame_BB_C1 : C_CreateGame { uint8_t solo_mode; uint8_t unused2[3]; }; // C2 (C->S): Set choice search parameters struct C_SetChoiceSearchParameters_C2 { le_uint16_t disabled; // 0 = enabled, 1 = disabled le_uint16_t unused; struct Entry { le_uint16_t parent_category_id; le_uint16_t category_id; }; Entry entries[0]; }; // C3 (C->S): Execute choice search struct C_ExecuteChoiceSearch_C3 { le_uint32_t unknown; struct Entry { le_uint16_t parent_category_id; le_uint16_t category_id; }; Entry entries[0]; }; // C4 (S->C): Choice search results // Command is a list of these; header.flag is the entry count struct S_ChoiceSearchResultEntry_GC_C4 { le_uint32_t guild_card_number; ptext name; // No language marker, as usual on GC ptext info_string; // Usually something like " Lvl " // Format is stricter here; this is "LOBBYNAME,BLOCKNUM,SHIPNAME" // If target is in game, for example, "Game Name,BLOCK01,Alexandria" // If target is in lobby, for example, "BLOCK01-1,BLOCK01,Alexandria" ptext locator_string; // Server IP and port for "meet user" option le_uint32_t server_ip; le_uint16_t server_port; le_uint16_t unused1; le_uint32_t menu_id; le_uint32_t lobby_id; // These two are guesses le_uint32_t game_id; // Zero if target is in a lobby rather than a game parray unused2; }; // C5 (S->C): Challenge rank update // TODO: Document format for this command // C6 (C->S): Set blocked senders list struct C_SetBlockedSenders_C6 { parray blocked_senders; }; // C7 (C->S): Enable simple mail auto-reply // 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 // CA (C->S): Ep3 server data request // CB: Broadcast command (Episode 3) // Same as 60, but only send to Episode 3 clients // CC: Invalid command // CD: Invalid command // CE: Invalid command // CF: Invalid command // 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 // Format unknown // D1 (S->C): Confirm trade to initiator // No arguments // D2: Invalid command // D3 (S->C): Execute trade with accepter // Format unknown; appears to be same as D0 // D4 (S->C): Close trade // No arguments // D5: Large message box // 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. // D7 (C->S): Request GBA game file struct C_GBAGameRequest_GC_D7 { ptext filename; }; // D8 (S->C): Info board // Command is a list of these; header.flag is the entry count template struct S_InfoBoardEntry_D8 { ptext name; ptext message; }; 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 // DA (S->C): Change lobby event // Header flag = new event number. No other arguments // DB (S->C): Verify license (GC) struct C_VerifyLicense_GC_DB { ptext unused; ptext serial_number; ptext access_key; ptext unused2; le_uint32_t sub_version; ptext serial_number2; ptext access_key2; ptext password; }; // DC: Player menu state (Episode 3) // No arguments. Client expects server to respond with command = DC, flag = 0 // DC: Guild card data (BB) struct C_GuildCardDataRequest_BB_DC { le_uint32_t unknown; le_uint32_t chunk_index; le_uint32_t cont; }; struct S_GuildCardHeader_BB_01DC { le_uint32_t unknown; // should be 1 le_uint32_t filesize; // 0x0000490 le_uint32_t checksum; }; // Command 02DC is used to send the guild card file data. It goes like this: // uint32_t unknown; // 0 // uint32_t chunk_index; // uint8_t data[0x6800, or less if last chunk] // DD: Invalid command // DE: Unknown (used by BB) // DF: Invalid command // E0 (S->C): Tournament list (Episode 3) // Command is a list of these; header.flag is the entry count. Some servers // always send 0x740 bytes even if the entries don't fill the space. struct S_TournamentEntry_GC_Ep3_E0 { le_uint32_t menu_id; le_uint32_t item_id; parray unknown; }; // E0 (C->S): Request team and key config (BB) // E1: Invalid command // E2 (C->S): Tournament control (Episode 3) // Flag = 0 => request tournament list (server responds with E0) // Flag = 1 => check tournament // Flag = 2 => cancel tournament entry // Flag = 3 => create tournament spectator team // Flag = 4 => join tournament spectator team // E2 (S->C): Tournament entry control (Episode 3) // E2 (S->C): Team and key config (BB) // See KeyAndTeamConfigBB in Player.hh for format // E3: Player previews (BB) struct C_PlayerPreviewRequest_BB_E3 { le_uint32_t player_index; le_uint32_t unused; }; struct S_PlayerPreview_BB_E3 { le_uint32_t player_index; PlayerDispDataBBPreview preview; }; // E4: CARD lobby game (Episode 3) // Header flag = seated state (1 = present, 0 = leaving) struct C_CardLobbyGame_GC_E4 { le_uint16_t table_number; le_uint16_t seat_number; }; // Header flag = 2 struct S_CardLobbyGame_GC_E4 { struct Entry { le_uint32_t present; // 1 = player present, 0 = no player le_uint32_t guild_card_number; }; Entry entries[4]; }; // E4 (S->C): Player choice or no player present (BB) struct S_ApprovePlayerChoice_BB_00E4 { le_uint32_t player_index; le_uint32_t unused; }; struct S_PlayerPreview_NoPlayer_BB_E4 { le_uint32_t player_index; le_uint32_t error; }; // E5 (C->S): Create character (BB) struct C_CreateCharacter_BB_E5 { le_uint32_t player_index; PlayerDispDataBBPreview preview; }; // E6: Spectator team list (Episode 3) // Same format as 08 command // E6 (S->C): Set guild card number and update client config (BB) struct S_ClientInit_BB_E6 { le_uint32_t error; le_uint32_t player_tag; le_uint32_t guild_card_number; le_uint32_t team_id; ClientConfigBB cfg; le_uint32_t caps; // should be 0x00000102 }; // E7: Save or load full player data // See export_bb_player_data() in Player.cc for format // E8 (C->S): Client checksum (BB) // E8 (S->C): Accept client checksum (BB) struct S_AcceptClientChecksum_BB_02E8 { le_uint32_t verify; le_uint32_t unused; }; // E9: Invalid command // EA: Team control (BB) // EB: Send stream file index and chunks // Command is a list of these; header.flag is the entry count. struct S_StreamFileIndexEntry_BB_01EB { le_uint32_t size; le_uint32_t checksum; le_uint32_t offset; ptext filename; } __attribute__((packed)); struct S_StreamFileChunk_BB_02EB { le_uint32_t chunk_index; uint8_t data[0x6800]; }; // EC: Create game (Episode 3) // Same format as C1; some fields are unused (e.g. episode, difficulty) // TODO: Where does the "view battle allowed" flag end up? // EC: Leave character select (BB) // ED (C->S): Update account data (BB) // TODO: Actually define these structures and don't just treat them as raw data union C_UpdateAccountData_BB_ED { le_uint32_t option; // 01ED parray symbol_chats; // 02ED parray chat_shortcuts; // 03ED parray key_config; // 04ED parray pad_config; // 05ED parray tech_menu; // 06ED parray customize; // 07ED } __attribute__((packed)); // EE: Scrolling message (BB) // Contents are plain text (char16_t) // EF: Invalid command // F0: Invalid command // F1: Invalid command // F2: Invalid command // F3: Invalid command // F4: Invalid command // F5: Invalid command // F6: Invalid command // F7: Invalid command // F8: Invalid command // F9: Invalid command // FA: Invalid command // FB: Invalid command // FC: Invalid command // FD: Invalid command // FE: Invalid command // FF: Invalid command struct S_SendGuildCard_GC { uint8_t subcommand; uint8_t subsize; le_uint16_t unused; le_uint32_t player_tag; le_uint32_t serial_number; ptext name; ptext desc; uint8_t reserved1; uint8_t reserved2; uint8_t section_id; uint8_t char_class; }; struct S_SendGuildCard_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t unused; le_uint32_t serial_number; ptext name; ptext team_name; ptext desc; uint8_t reserved1; uint8_t reserved2; uint8_t section_id; uint8_t char_class; }; struct S_DropItem_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t unused; uint8_t area; uint8_t dude; le_uint16_t request_id; float x; float y; le_uint32_t unused2; ItemData data; }; struct S_DropStackedItem_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t unused; le_uint16_t area; le_uint16_t unused2; float x; float y; le_uint32_t unused3; ItemData data; }; struct S_PickUpItem_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t client_id; le_uint16_t client_id2; le_uint16_t area; le_uint32_t item_id; }; struct S_CreateInventoryItem_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t client_id; ItemData item; le_uint32_t unused; }; struct S_DestroyItem_BB { uint8_t subcommand; uint8_t subsize; le_uint16_t client_id; le_uint32_t item_id; le_uint32_t amount; }; struct S_BankContentsHeader_BB { uint8_t subcommand; uint8_t unused1; le_uint16_t unused2; le_uint32_t size; // same as size in overall command header le_uint32_t checksum; // can be random; client won't notice le_uint32_t numItems; le_uint32_t meseta; }; struct S_ShopContents_BB { uint8_t subcommand; // B6 uint8_t size; // 2C regardless of the number of items?? le_uint16_t params; // 037F uint8_t shop_type; uint8_t num_items; le_uint16_t unused; ItemData entries[20]; }; #pragma pack(pop)