diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index b18da279..777c92fd 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -17,45 +17,36 @@ // This file is newserv's canonical reference of the PSO client/server protocol. -// For the unfamiliar, the le_uint and be_uint types (from phosg/Encoding.hh) -// are the same as normal uint types, but are explicitly little-endian or -// big-endian. The parray type (from Text.hh) is the same as a standard array, -// but has various safety and convenience features so we don't have to use -// easy-to-mess-up functions like memset/memcpy and strncpy. The pstring types -// (also from Text.hh) are like std::strings, but have an explicit encoding. -// They can be implicitly converted to and from std::strings, and will encode -// or decode their specified encoding when doing so. (The default encoding is -// UTF-8 everywhere in the server code.) +// For the unfamiliar, the le_uint and be_uint types (from phosg/Encoding.hh) are the same as normal uint types, but +// are explicitly little-endian or big-endian. The parray type (from Text.hh) is the same as a standard array, but has +// various safety and convenience features so we don't have to use easy-to-mess-up functions like memset/memcpy and +// strncpy. The pstring types (also from Text.hh) are like std::strings, but have an explicit encoding. They can be +// implicitly converted to and from std::strings, and will encode or decode their specified encoding when doing so. +// (The default encoding is UTF-8 everywhere in the server code.) -// Struct 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. +// Struct 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. The version tokens are: +// DCv1 = PSO Dreamcast v1 +// DCv2 = PSO Dreamcast v2 +// DC = Both DCv1 and DCv2 +// PC = PSO PC (v2) +// GC = PSO GC Episodes 1&2 and/or Episode 3 +// XB = PSO Xbox Episodes 1&2 +// V3 = PSO GC and PSO Xbox (these versions are similar and share many formats) +// BB = PSO Blue Burst -// The version tokens are as follows: -// DCv1 = PSO Dreamcast v1 -// DCv2 = PSO Dreamcast v2 -// DC = Both DCv1 and DCv2 -// PC = PSO PC (v2) -// GC = PSO GC Episodes 1&2 and/or Episode 3 -// XB = PSO Xbox Episodes 1&2 -// BB = PSO Blue Burst -// V3 = PSO GC and PSO Xbox (these versions are similar and share many formats) - -// For variable-length commands, generally a zero-length array is included on -// the end of the struct if the command is received by newserv, and is omitted -// if it's sent by newserv. In the latter case, we often use StringWriter to +// For variable-length commands, a zero-length array is sometimes included on the end of the struct if the command is +// received by newserv, and is omitted if it's sent by newserv. In the latter case, we often use StringWriter to // construct the command data instead. -// Structures are sorted by command number. Long BB commands are placed in order -// according to their low byte; for example, command 01EB is in position as EB. +// Structures are sorted by command number. Long BB commands are placed in order according to their low byte; for +// example, command 01EB is in position as EB. // Text escape codes -// Most text fields allow the use of various escape codes to change decoding, -// change color, or create symbols. These escape codes are always preceded by a -// tab character (0x09, or '\t'). For brevity, we generally refer to them with $ -// instead in newserv, since the server substitutes most usage of $ in player- -// provided text with \t. The escape codes are: +// Most text fields allow the use of various escape codes to change decoding, change color, or create symbols. These +// escape codes are always preceded by a tab character (0x09, or '\t'). For brevity, we generally refer to them with $ +// in newserv, since the server substitutes most usage of $ in player-provided text with \t. The escape codes are: // - Language codes // - - $E: Set text interpretation to English / use Roman font // - - $J: Set text interpretation to Japanese / use Japanese font @@ -90,14 +81,13 @@ // - - $r: Right arrow // - - $u: Up arrow -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// PATCH SERVER COMMANDS /////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PATCH SERVER COMMANDS ////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// The patch protocol is identical between PSO PC and PSO BB (the only versions -// on which it is used). +// The patch protocol is identical between PSO PC and PSO BB (the only versions on which it is used). // A patch server session generally goes like this: // Server: 02 (unencrypted) @@ -140,19 +130,17 @@ // 02 (S->C): Start encryption // Client will respond with an 02 command. -// All commands after this command will be encrypted with PSO V2 encryption. -// If this command is sent during an encrypted session, the client will not -// reject it; it will simply re-initialize its encryption state and respond with +// All commands after this command will be encrypted with PSO V2 encryption. If this command is sent during an +// encrypted session, the client will not reject it; it will simply re-initialize its encryption state and respond with // an 02 as normal. -// The copyright field in the below structure must contain the following text: -// "Patch Server. Copyright SonicTeam, LTD. 2001" +// The copyright field in the below structure must contain "Patch Server. Copyright SonicTeam, LTD. 2001". struct S_ServerInit_Patch_02 { pstring copyright; le_uint32_t server_key = 0; // Key for commands sent by server le_uint32_t client_key = 0; // Key for commands sent by client - // The client rejects the command if it's larger than this size, so we can't - // add the after_message like we do in the other server init commands. + // The client rejects the command if it's larger than this size, so we can't add the after_message like we do in the + // other server init commands. } __packed_ws__(S_ServerInit_Patch_02, 0x48); // 02 (C->S): Encryption started @@ -161,12 +149,11 @@ struct S_ServerInit_Patch_02 { // 03: Invalid command // 04 (S->C): Request login information -// No arguments -// Client will respond with an 04 command. +// No arguments. Client will respond with an 04 command. // 04 (C->S): Log in (patch) -// The email field is always blank on BB. It may be blank on PC too, so this -// cannot be used to determine the game version used by a patch client. +// The email field is always blank on BB. It may be blank on PC too, so this cannot be used to determine the game +// version used by a patch client. struct C_Login_Patch_04 { parray unused; @@ -177,8 +164,8 @@ struct C_Login_Patch_04 { // 05 (S->C): Disconnect // No arguments -// This command is not used in the normal flow (described above). Generally the -// server should disconnect after sending a 12 or 15 command instead of an 05. +// This command is not used in the normal flow (described above). Generally the server should disconnect after sending +// a 12 or 15 command instead of an 05. // 06 (S->C): Open file for writing @@ -189,12 +176,10 @@ struct S_OpenFile_Patch_06 { } __packed_ws__(S_OpenFile_Patch_06, 0x38); // 07 (S->C): Write file -// The client's handler table says this command's maximum size is 0x6010 -// including the header, but the command may be shorter if the server chooses -// to use a shorter chunk size. Unlike the game server's 13 and A7 commands, -// the chunks do not need to be the same size - the game opens the file with -// the "a+b" mode each time it is written, so the new data is always appended -// to the end. +// The client's handler table says this command's maximum size is 0x6010 including the header, but the command may be +// shorter if the server chooses to use a shorter chunk size. Unlike the game server's 13 and A7 commands, the chunks +// do not need to be the same size - the game opens the file with the "a+b" mode each time it is written, so the new +// data is always appended to the end. struct S_WriteFileHeader_Patch_07 { le_uint32_t chunk_index = 0; @@ -204,9 +189,8 @@ struct S_WriteFileHeader_Patch_07 { } __packed_ws__(S_WriteFileHeader_Patch_07, 0x0C); // 08 (S->C): Close current file -// The unused field is optional. It's not clear whether this field was ever -// used; it could be a remnant from pre-release testing, or someone could have -// simply set the maximum size of this command incorrectly. +// The unused field is optional. It's not clear whether this field was ever used; it could be a remnant from +// pre-release testing, or someone could have simply set the maximum size of this command incorrectly. struct S_CloseCurrentFile_Patch_08 { le_uint32_t unused = 0; @@ -258,17 +242,14 @@ struct S_StartFileDownloads_Patch_11 { // No arguments // 13 (S->C): Message box -// Same format and usage as commands 1A/D5 on the game server (described below). -// On PSOBB, the message box appears in the upper half of the screen and -// functions like a normal PSO message box - that is, you can use color escapes -// (\tCG, for example) and lines are terminated with \n. On PSOPC, the message -// appears in a Windows edit control, so the text functions differently: line -// breaks must be \r\n and standard PSO color escapes don't work. The maximum -// size of this command is 0x2004 bytes, including the header. +// Same format and usage as commands 1A/D5 on the game server (described below). On PSOBB, the message box appears in +// the upper half of the screen and functions like a normal PSO message box - that is, you can use color escapes (\tCG, +// for example) and lines are terminated with \n. On PSOPC, the message appears in a Windows edit control, so the text +// functions differently: line breaks must be \r\n and standard PSO color escapes don't work. The maximum size of this +// command is 0x2004 bytes, including the header. // 14 (S->C): Reconnect -// Same format and usage as command 19 on the game server (described below), -// except the port field is big-endian for some reason. +// Same format and usage as command 19 on the game server (described below), except the port field is big-endian. template struct S_ReconnectT { @@ -280,30 +261,25 @@ using S_Reconnect_Patch_14 = S_ReconnectT; check_struct_size(S_Reconnect_Patch_14, 0x08); // 15 (S->C): Login failure -// No arguments -// The client shows a message like "Incorrect game ID or password" and -// disconnects. +// No arguments. The client shows a message like "Incorrect game ID or password" and disconnects. // No commands beyond 15 are valid on the patch server. -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// GAME SERVER COMMANDS //////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GAME SERVER COMMANDS /////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 00: Invalid command // 01 (S->C): Lobby message box // Internal name: RcvError -// A small message box appears in lower-right corner, and the player must press -// a key to continue. The maximum length of the message is 0x200 bytes. -// Internally, PSO calls this RcvError, since it's generally used to tell the -// player why they can't do something (e.g. join a full game). -// This format is shared by multiple commands; for all of them except 06 (S->C), -// the guild_card_number field is unused and should be 0. -// On BB, this command may be sent as 0001 or 0101; in the latter case, the -// message box appears in the lower-left corner instead. +// A small message box appears in lower-right corner, and the player must press a key to continue. The maximum length +// of the message is 0x200 bytes. Internally, PSO calls this RcvError, since it's generally used to tell the player why +// they can't do something (e.g. join a full game). This format is shared by multiple commands; for all of them except +// 06 (S->C), the guild_card_number field is unused and should be 0. On BB, this command may be sent as 0001 or 0101; +// in the latter case, the message box appears in the lower-left corner instead. struct SC_TextHeader_01_06_11_B0_EE { le_uint32_t unused = 0; @@ -313,20 +289,17 @@ struct SC_TextHeader_01_06_11_B0_EE { // 02 (S->C): Start encryption (except on BB) // Internal name: RcvPsoConnectV2 -// This command should be used for non-initial sessions (after the client has -// already selected a ship, for example). Command 17 should be used instead for -// the first connection. -// All commands after this command will be encrypted with PSO V2 encryption on -// DC, PC, and GC Episodes 1&2 Trial Edition, or PSO V3 encryption on other V3 -// versions. -// DCv1 clients will respond with an (encrypted) 93 command. -// DCv2 and PC clients will respond with an (encrypted) 9A or 9D command. -// V3 clients will respond with an (encrypted) 9A or 9E command, except for GC -// Episodes 1&2 Trial Edition, which behaves like PC. -// The copyright field in the below structure must contain the following text: -// "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999" -// (The above text is required on all versions that use this command, including -// those versions that don't run on the DreamCast.) +// This command should be used for non-initial sessions (after the client has already selected a ship, for example). +// Command 17 should be used instead for the first connection. +// All commands after this command will be encrypted with PSO V2 encryption on DC, PC, and GC Episodes 1&2 Trial +// Edition, or PSO V3 encryption on other V3 versions. The (encrypted) response depends on the client's version: +// - DC NTE clients will respond with 8B. +// - DC 11/2000 and DCv1 clients will respond with 93. +// - DCv2, PCv2, and GC NTE clients will respond with an 9A, 9D, or 9E. +// - V3 clients will respond with 9A or 9E command. +// The copyright field in the below structure must contain "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999". +// (The above text is required on all versions that use this command, including those versions that don't run on the +// DreamCast.) struct S_ServerInitDefault_DC_PC_V3_02_17_91_9B { pstring copyright; @@ -337,8 +310,8 @@ struct S_ServerInitDefault_DC_PC_V3_02_17_91_9B { template struct S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B { S_ServerInitDefault_DC_PC_V3_02_17_91_9B basic_cmd; - // This field is not part of SEGA's implementation; the client ignores it. - // newserv sends a message here disavowing the preceding copyright notice. + // This field is not part of SEGA's implementation; the client ignores it. newserv sends a message here disavowing + // the preceding copyright notice. pstring after_message; } __attribute__((packed)); @@ -351,10 +324,9 @@ struct C_LegacyLogin_PC_V3_03 { /* 0C */ uint8_t is_extended = 0; /* 0D */ Language language = Language::JAPANESE; /* 0E */ le_uint16_t unused = 0; - // Note: These are suffixed with 2 since they come from the same source data - // as the corresponding fields in 9D/9E. (Even though serial_number and - // serial_number2 have the same contents in 9E, they do not come from the same - // field on the client's connection context object.) + // Note: These are suffixed with 2 since they come from the same source data as the corresponding fields in 9D/9E. + // (Even though serial_number and serial_number2 have the same contents in 9E, they do not come from the same field + // on the client's connection context object.) /* 10 */ pstring serial_number2; /* 20 */ pstring access_key2; /* 30 */ @@ -362,22 +334,16 @@ struct C_LegacyLogin_PC_V3_03 { // 03 (S->C): Legacy register result (non-BB) // Internal name: RcvRegist -// header.flag specifies if the password was correct. If header.flag is 0, the -// password saved to the memory card (if any) is deleted and the client is -// disconnected. If header.flag is nonzero, the client responds with an 04 -// command. Curiously, it looks like even DCv1 doesn't use this command in its -// standard login sequence, so this may be a relic from very early development. -// Even more curiously, DCv2 (and no other PSO version) has a behavior that -// appears to be some kind of anti-cheating mechanism: if any byte in the memory -// range 8C004000-8C007FFF is not zero, the handler for this command loops -// infinitely doing nothing. -// No other arguments +// No arguments except for header.flag, which specifies if the password was correct. If header.flag is 0, the password +// saved to the memory card (if any) is deleted and the client is disconnected. If header.flag is nonzero, the client +// responds with an 04 command. Curiously, it looks like even DC NTE doesn't use this command in its standard login +// sequence, so this may be a relic from very early development. Even more curiously, DCv2 (and no other PSO version) +// has a behavior that appears to be some kind of anti-cheating mechanism: if any byte in the memory range 8C004000- +// 8C007FFF is not zero, the handler for this command loops infinitely doing nothing. // 03 (S->C): Start encryption (BB) -// Client will respond with an (encrypted) 93 command. -// All commands after this command will be encrypted with PSO BB encryption. -// The copyright field in the below structure must contain the following text: -// "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM." +// All commands after this will be encrypted with PSO BB encryption. Client will respond with an encrypted 93 command. +// The copyright field must contain "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.". struct S_ServerInitDefault_BB_03_9B { pstring copyright; @@ -394,11 +360,9 @@ struct S_ServerInitWithAfterMessageT_BB_03_9B { // 04 (C->S): Legacy login // Internal name: SndLogin2 -// Curiously, there is a SndLogin3 function, but it does not send anything. -// See comments on non-BB 03 (S->C). This is likely a relic of an older, -// now-unused sequence. Like 03, this command isn't used by any known PSO -// version. // header.flag is 1 if the client has UDP disabled. +// Curiously, there is a SndLogin3 function, but it does not send anything. See comments on non-BB 03 (S->C). This is +// likely a relic of an older, now-unused sequence. Like 03, this command isn't used by any known PSO version. struct C_LegacyLogin_PC_V3_04 { /* 00 */ be_uint64_t hardware_id; @@ -423,9 +387,8 @@ struct C_LegacyLogin_BB_04 { // 04 (S->C): Set guild card number and update client config ("security data") // Internal name: RcvLogin -// header.flag specifies an error code; the format described below is only used -// if this code is 0 (no error). Otherwise, the command has no arguments. -// Error codes (on GC): +// header.flag specifies an error code; the format described below is only used if this code is 0 (no error). +// Otherwise, the command has no arguments. Error codes (on GC): // 01 = Line is busy (103) // 02 = Already logged in (104) // 03 = Incorrect password (106) @@ -433,29 +396,19 @@ struct C_LegacyLogin_BB_04 { // 05 = Server down for maintenance (108) // 06 = Incorrect password (127) // Any other nonzero value = Generic failure (101) -// The client config field in this command is ignored by all clients that never -// send 9E. Clients that do send 9E will save thie client config as opaque data -// to be returned in a 9E or 9F command later. -// The 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. 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. -// BB clients have multiple client configs; this command sets the client config -// that is returned by the 9E and 9F commands, but does not affect the client -// 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. +// The client config field in this command is ignored by all clients that never send 9E. Clients that do send 9E will +// save the client config as opaque data to be returned in a 9E or 9F command later. The 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. BB clients have multiple client configs; this command sets the client config +// that is returned by the 9E and 9F commands, but does not affect the client 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. 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 - // which denotes whether the player is present or not (for example, in lobby - // data structures). For historical and simplicity reasons, newserv combines - // these three fields into one, which takes on the value 0x00010000 when a - // player is present and zero when none is present. + // 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 which + // denotes whether the player is present or not (for example, in lobby data structures). For historical and + // simplicity reasons, newserv combines these three fields into one, which takes the value 0x00010000 when a player + // is present and zero when none is present. le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; } __packed_ws__(S_UpdateClientConfig_DC_PC_04, 8); @@ -463,8 +416,7 @@ struct S_UpdateClientConfig_DC_PC_04 { 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). + // This field is opaque to the client; it will send back the contents verbatim in subsequent 9E or 9F commands. parray client_config; } __packed_ws__(S_UpdateClientConfig_V3_04, 0x28); @@ -476,97 +428,76 @@ struct S_UpdateClientConfig_BB_04 { // 05: Disconnect // Internal name: SndLogout -// No arguments -// Sending this command to a client will cause it to disconnect. There's no -// advantage to doing this over simply closing the TCP connection. Clients will -// send this command to the server when they are about to disconnect, but the -// server does not need to close the connection when it receives this command -// (and in some cases, the client will send multiple 05 commands before actually -// disconnecting). +// No arguments. Sending this command to a client will cause it to disconnect. There's no advantage to doing this over +// simply closing the TCP connection. Clients will send this command to the server when they are about to disconnect, +// but the server does not need to close the connection when it receives this command (and in some cases, the client +// will send multiple 05 commands before actually disconnecting). // 06: Chat // Internal name: RcvChat and SndChat -// Server->client format is the same as the 01 command; guild_card_number -// is unused and set to zero. The maximum size of the message is 0x200 bytes. -// Client->server format is the same as the 01 command also. -// When sent by the client, the text field includes only the message. When sent -// by the server, the text field includes the origin player's name, followed by -// a tab character (\x09), followed by the message. -// During Episode 3 battles, the first byte of an inbound 06 command's message -// is interpreted differently. It should be treated as a bit field, with the low -// 4 bits intended as masks for who can see the message. If the low bit (1) is -// set, for example, then the chat message displays as " (whisper)" on player -// 0's screen regardless of the message contents. The next bit (2) hides the -// message from player 1, etc. The high 4 bits of this byte appear not to be -// used, but are often nonzero and set to the value 4. (This is probably done so -// that the field is always a valid ASCII character and also never terminates -// the chat string accidentally.) We call this byte private_flags in the places -// where newserv uses it; there is a similar byte in the 6xBD (private word -// select) command. +// Server->client and client->server formats are both the same as the 01 (S->C) command. When sent by the client, +// guild_card_number is unused and set to zero. The maximum size of the message is 0x200 bytes. +// When sent by the client, the text field includes only the message. When sent by the server, the text field includes +// the origin player's name, followed by a tab character (\x09), followed by the message. +// During Episode 3 battles, the first byte of an inbound 06 command's message is interpreted differently. It should be +// treated as a bit field, with the low 4 bits intended as masks for who can see the message. If the low bit (1) is +// set, for example, then the chat message displays as " (whisper)" on player 0's screen regardless of the message +// contents. The next bit (2) hides the message from player 1, etc. The high 4 bits of this byte appear not to be used, +// but are often nonzero and set to the value 4. (This is probably done so that the field is always a valid ASCII +// character and also never terminates the chat string accidentally.) We call this byte private_flags in the places +// where newserv uses it; there is a similar byte in the 6xBD (private word select) command. // 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 -// servers except newserv, it seems). Curiously, the string "RcvBlockList" -// appears in PSO v1 and v2, but it is not used, implying that at some point -// 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, whch uses 07/8E/8F instead). +// 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 servers except newserv, it seems). Curiously, the string "RcvBlockList" appears +// in PSO v1 and v2, but it is not used, implying that at some point 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, whch uses 07/8E/8F instead). -// In all PSO versions except DC NTE and 11/2000, this command sets the -// interaction mode, which affects which objects can be interacted with and -// what certain controls do. It also affects some in-game behaviors; for -// example, if the leader's interaction mode is set incorrectly in a game, the -// leader will not send the game state to joining players, so they will wait -// forever and not be able to actually join. +// The menu is titled "Ship Select" unless the first menu item begins with the text "BLOCK" (all caps), in which case +// it is titled "Block Select". -// In DC NTE and 11/2000, a side effect of this command not setting the -// interaction mode is that the menu doesn't work properly unless another -// command that sets the correct interaction mode (1) is sent before it. The -// user-visible effects of the interaction mode not being set are that the -// "Please select a ship" message doesn't appear, the X button does nothing, -// and selecting an item from the menu doesn't dismiss the menu and instead -// softlocks, since the client doesn't send anything.) One such command that -// sets the correct interaction mode is command 04 with an error code of 0. +// In all PSO versions except DC NTE and 11/2000, this command sets the interaction mode, which affects which objects +// can be interacted with and what certain controls do. It also affects some in-game behaviors; for example, if the +// leader's interaction mode is set incorrectly in a game, the leader will not send the game state to joining players, +// so they will wait forever and not be able to actually join. -// The menu is titled "Ship Select" unless the first menu item begins with the -// text "BLOCK" (all caps), in which case it is titled "Block Select". +// In DC NTE and 11/2000, a side effect of this command not setting the interaction mode is that the menu doesn't work +// properly unless another command that sets the correct interaction mode (1) is sent before it. The user-visible +// effects of the interaction mode not being set are that the "Please select a ship" message doesn't appear, the X +// button does nothing, and selecting an item from the menu doesn't dismiss the menu and instead softlocks, since the +// client doesn't send anything. One such command that sets the correct interaction mode is command 04 with an error +// code of 0, so on pre-v1 versions, an 07 command must be preceded by an 04 command. -// 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 first -// entry's text becomes the ship name displayed in the corner of the lobby HUD, +// 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 first entry's text becomes the ship name displayed in the corner of the lobby HUD, // unless it begins with "BLOCK", in which case it is ignored. template struct S_MenuItemT { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; - // The following two fields are only used for the game menu; for Ship Select, - // Block Select, and Information, they are unused. - // difficulty_tag is 0x0A on Episode 3; on all other versions, it's - // difficulty + 0x22 (so 0x25 means Ultimate, for example). + // The following two fields are only used for the game menu; for Ship Select, Block Select, and Information, they are + // unused. difficulty_tag is 0x0A on Episode 3; on all other versions, it's difficulty + 0x22 (so 0x25 means + // Ultimate, for example). uint8_t difficulty_tag = 0; uint8_t num_players = 0; pstring name; - // The episode field is only used for the game menu; for Ship Select, Block - // Select, and Information, it is unused. + // The episode field is only used for the game menu; for Ship Select, Block Select, and Information, it is unused. // The episode field is used differently by different versions: // - On DCv1, PC, and GC Episode 3, the value is ignored. // - On DCv2, 1 means v1 players can't join the game, and 0 means they can. // - On GC Ep1&2, 0x40 means Episode 1, and 0x41 means Episode 2. // - On BB, 0x40/0x41 mean Episodes 1/2 as on GC, and 0x43 means Episode 4. uint8_t episode = 0; - // Flags (01 and 02 are used for all menus; the rest are only used for the - // game menu): - // 01 = Send name? (client sends the name field in the 10 command if this - // item is chosen, but it's blank) - // 02 = Locked (lock icon appears in menu; player is prompted for password if - // they choose this game) - // 04 = In battle (Episode 3; a sword icon appears in menu) - // 04 = Disabled (BB; used for solo games) + // Flags (01 and 02 are used for all menus; the rest are only used for the game menu): + // 01 = Send name? (client sends the name field in the 10 command if this item is chosen, but it's blank) + // 02 = Locked (lock icon appears in the menu; player is prompted for a password if they choose this game) + // 04 = In battle (Episode 3 only; a sword icon appears in the menu) + // 04 = Disabled (BB only; used for solo games) // 10 = Is battle mode // 20 = Is challenge mode // 40 = Is v2 only (DCv2/PC); game name renders in orange @@ -587,13 +518,12 @@ check_struct_size(S_MenuItem_DC_V3_08_Ep3_E6, 0x1C); // 08 (S->C): Game list // Internal name: RcvGameList // Client responds with 09 and 10 commands (or nothing if the player cancels). -// Command format is the same as 07. Like 07, this command also sets the -// interaction mode, so it should not be used within a game. +// Command format is the same as 07. Like 07, this command also sets the interaction mode, so it should not be used +// within a game. // 09 (C->S): Menu item info request // Internal name: SndInfo -// Server will respond with an 11 command, or an A3 or A5 if the specified menu -// is the quest menu. +// Server will respond with an 11 command, or an A3 or A5 if the specified menu is the quest menu. struct C_MenuItemInfoRequest_09 { le_uint32_t menu_id = 0; @@ -603,8 +533,7 @@ struct C_MenuItemInfoRequest_09 { // 0B: Invalid command // 0C (C->S): Create game (DCv1) -// Same format as C1, but fields not supported by v1 (e.g. episode, v2 mode) -// are unused. +// Same format as C1, but fields not supported by v1 (e.g. episode, v2 mode) are unused. // 0D: Invalid command @@ -612,19 +541,15 @@ struct C_MenuItemInfoRequest_09 { // Internal name: RcvStartGame // header.flag = number of valid entries in lobby_data -// This command appears to be a vestige of very early development; its -// second-phase handler is missing even in the earliest public prototype of PSO -// (DC NTE), and the command format is missing some important information -// necessary to start a game on any version. +// This command appears to be a vestige of very early development; its second-phase handler is missing even in the +// earliest public prototype of PSO (DC NTE), and the command format is missing some important information necessary to +// start a game on any version. -// There is a failure mode in the 0E command handler that causes the thread -// receiving the command to loop infinitely doing nothing, effectively -// softlocking the game. This happens if the local player's Guild Card number -// doesn't match any of the lobby_data entries. (Notably, only the first -// (header.flag) entries are checked.) -// If the local player's Guild Card number does match one of the entries, the -// command does not softlock, but instead does nothing because the 0E -// second-phase handler is missing. +// There is a failure mode in the 0E command handler that causes the thread receiving the command to loop infinitely +// doing nothing, effectively softlocking the game. This happens if the local player's Guild Card number doesn't match +// any of the lobby_data entries. (Notably, only the first (header.flag) entries are checked.) If the local player's +// Guild Card number does match one of those entries, the command does not softlock, but instead does nothing because +// the 0E second-phase handler is missing. template struct SC_MeetUserExtensionT { @@ -674,26 +599,25 @@ struct S_LegacyJoinGame_XB_0E { // 10 (C->S): Menu selection // Internal name: SndAction -// header.flag has different meanings depending on which menu is selected. -// For most menus, header.flag is a bit field containing two flags: 02 -// specifies if a password is present, and 01 specifies if a name is present. -// (If both are set, the name comes first, as described below). These two bits -// directly correspond to the two lowest bits in the flags field of the game -// menu: 02 specifies that the game is locked, but the function of 01 is -// unknown. The ability to send a name along with a menu choice is unused in -// all client versions except Episode 3, where it's used in the tournament -// entries menu. It's not clear why all other versions have the ability send a -// name here - it may be a relic from very early development. -// For the quest categories menu, header.flag specifies the player's -// progression through the story. The values are: -// 0 = has not yet defeated Dragon -// 1 = has defeated Dragon but not De Rol Le -// 2 = has defeated De Rol Le but not Vol Opt -// 3 = has defeated Vol Opt but not Dark Falz -// 4 = has defeated Dark Falz -// For the challenge categories menu, header.flag specifies something related -// to challenge stage completion (TODO: reverse-engineer function at -// 59NL:004DA300 to see what this is) + +// header.flag has different meanings depending on which menu is selected. For most menus, header.flag is a bit field +// containing two flags: 02 specifies if a password is present, and 01 specifies if a name is present. (If both are +// set, the name comes first, as described below). These two bits directly correspond to the two lowest bits in the +// flags field of the game menu: 02 specifies that the game is locked, but the function of 01 is unknown. The ability +// to send a name along with a menu choice is unused in all client versions except Episode 3, where it's used in the +// tournament entries menu. It's not clear why all other versions have the ability send a name here - it may be a relic +// from very early development. + +// For the quest categories menu, header.flag specifies the player's progression through the story. The values are, in +// increasing order of priority: +// 0 = has not yet defeated Dragon (none of the below flags set) +// 1 = has defeated Dragon but not De Rol Le (quest flag 0x0017 set) +// 2 = has defeated De Rol Le but not Vol Opt (quest flag 0x0020 set) +// 3 = has defeated Vol Opt but not Dark Falz (quest flag 0x002A set) +// 4 = has defeated Dark Falz (quest flag 0x0033 set) +// For the challenge categories menu, header.flag specifies the first stage number in the current episode that any +// player in the game hasn't completed (that is, if there are 3 players and they have completed Challenge Mode through +// stages 1-3, 1-5, and 1-7 respectively, header.flag would be 4). struct C_MenuSelectionBase_10 { le_uint32_t menu_id = 0; @@ -730,35 +654,28 @@ check_struct_size(C_MenuSelectionWithNameAndPassword_PC_BB_10, 0x48); // 11 (S->C): Ship info // Internal name: RcvMessage -// Same format as 01 command. The text appears in a small box in the lower-left -// corner (on V3/BB) or lower-right corner of the screen. +// Same format as 01 command. The text appears in a small box in the lower-left corner (on V3/BB) or lower-right corner +// (on V1/V2) of the screen. // 12 (S->C): Valid but ignored (all versions) // Internal name: RcvBaner -// This command's internal name is possibly a misspelling of "banner", which -// could be an early version of the 1A/D5 (large message box) commands, or of -// BB's 00EE (scrolling message) command; however, the existence of -// RcvBanerHead (16) seems to contradict this hypothesis since a text message -// would not require a separate header command. Even on DC NTE, this command -// does nothing, so this must have been scrapped very early in development. +// This command's internal name is possibly a misspelling of "banner", which could be an early version of the 1A/D5 +// (large message box) commands, or of BB's 00EE (scrolling message) command; however, the existence of RcvBanerHead +// (16) seems to contradict this hypothesis since a text message would not require a separate header command. Even on +// DC NTE, this command does nothing, so this must have been scrapped very early in development. // 13 (S->C): Write online quest file // Internal name: RcvDownLoad -// Used for downloading online quests. For download quests (to be saved to the -// memory card), use A7 instead. -// This command exists on DC NTE, but it does nothing. DC NTE does not have the -// 44 command, which would also be required for loading quests, so online -// quests canot be loaded on DC NTE. -// All chunks except the last must have 0x400 data bytes. When downloading an -// online quest, the .bin and .dat chunks may be interleaved. There is a client -// bug in BB (and probably all other versions) where if the quest file's size -// is a multiple of 0x400, the last chunk will have size 0x400, and the client -// will never consider the download complete since it only checks if the last -// chunk has size < 0x400; it does not check if all expected bytes have been -// received. To work around this, newserv appends an extra zero byte if the -// quest file's size is a multiple of 0x400; this byte will be ignored since -// the PRS decompression algorithm contains a stop command, so it will never -// read it. +// Used for downloading online quests. For download quests (to be saved to the memory card), use A7 instead. +// This command exists on DC NTE, but it does nothing. DC NTE does not have the 44 command, which would also be +// required for loading quests, so online quests cannot be loaded on DC NTE. +// All chunks except the last must have 0x400 data bytes. When downloading an online quest, the .bin and .dat chunks +// may be interleaved. There is a client bug in BB (and probably all other versions) where if the quest file's size is +// a multiple of 0x400, the last chunk will have size 0x400, and the client will never consider the download complete +// since it only considers a file donwload complete when it receives a chunk with size < 0x400 (instead of checking if +// all expected bytes have been received). To work around this, newserv appends an extra zero byte if the quest file's +// size is a multiple of 0x400; this byte will be ignored since the PRS decompression algorithm contains a stop +// command, so the client will never read this extra byte when decompressing the quest. // header.flag = file chunk index (start offset / 0x400) struct S_WriteFile_13_A7 { @@ -768,8 +685,7 @@ struct S_WriteFile_13_A7 { } __packed_ws__(S_WriteFile_13_A7, 0x414); // 13 (C->S): Confirm file write (V3/BB) -// Client sends this in response to each 13 sent by the server. It appears -// these are only sent by V3 and BB - PSO DC and PC do not send these. +// The client sends this in response to each 13 sent by the server. This is not sent by clients before V3. // header.flag = file chunk index (same as in the 13/A7 sent by the server) struct C_WriteFileConfirmation_V3_BB_13_A7 { @@ -778,50 +694,43 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 { // 14 (S->C): Valid but ignored (all versions) // Internal name: RcvUpLoad -// Based on its internal name, this command seems like the logical opposite of -// 13 (quest file download, named RcvDownLoad internally). However, even in DC -// NTE, this command does nothing, so it must have been scrapped very early in -// development. There is a SndUpLoad string in the DC versions, but the -// corresponding function was deleted. +// Based on its internal name, this command seems like the logical opposite of 13. However, even in DC NTE, this +// command does nothing, so it must have been scrapped very early in development. There is a SndUpLoad string in the DC +// versions, but the corresponding function appears to have been deleted. // 15: Invalid command // 16 (S->C): Valid but ignored (all versions) // Internal name: RcvBanerHead -// It's not clear what this command was supposed to do, but it's likely related -// to 12 in some way. Like 12, this command does nothing, even on DC NTE. +// It's not clear what this command was supposed to do, but it's likely related to 12 in some way. Like 12, this +// command does nothing, even on DC NTE. // 17 (S->C): Start encryption at login server (except on BB) // Internal name: RcvPsoRegistConnectV2 -// Same format and usage as 02 command, but a different copyright string: -// "DreamCast Port Map. Copyright SEGA Enterprises. 1999" -// Unlike the 02 command, V3 clients will respond with a DB command when they -// receive a 17 command in any online session, with the exception of Episodes -// 1&2 trial edition (which responds with a 9A). DCv1 will respond with a 90. DC -// NTE will respond with an 8B. Other non-V3 clients will respond with a 9A or -// 9D. +// Same format as 02 command, but a different copyright: "DreamCast Port Map. Copyright SEGA Enterprises. 1999" +// The response depends on the client's version: +// - DC NTE will respond with 8B. +// - DC 11/2000 and DCv1 will respond with 90. +// - DCv2, PCv2, and GC NTE clients will respond with 9A or 9D. +// - V3 (GC/Xbox) clients will respond with a DB command when they receive a 17 command in any online // 18 (S->C): Account verification result (PC/V3) // Behaves exactly the same as 9A (S->C). No arguments except header.flag. // 19 (S->C): Reconnect to different address // Internal name: RcvPort -// Client will disconnect, and reconnect to the given address/port. Encryption -// will be disabled on the new connection; the server should send an appropriate -// command to enable it when the client connects. -// Note: PSO XB seems to ignore the address field, which makes sense given its -// networking architecture. +// The client will disconnect, then reconnect to the given address/port. Encryption will be disabled on the new +// connection; the server should send an appropriate command to enable it when the client connects. PSO Xbox seems to +// ignore the address field, which makes sense given its networking architecture. using S_Reconnect_19 = S_ReconnectT; check_struct_size(S_Reconnect_19, 8); -// Sylverant implements an IPv6 version of this command, but it's not obvious -// why. IPv6 technically did exist as a draft standard at the time of PSO's -// development, but it wasn't widely used until over a decade later. IPv6 -// support is not implemented in any version of the PSO client that I've seen, -// but we implement Sylverant's version of this command anyway because newserv -// may connect to Sylverant via IPv6 when using the proxy. Sylverant sends the -// value 6 in header.flag in this case, presumably to indicate the protocol. +// Sylverant implements an IPv6 version of this command, but it's not obvious why. IPv6 technically did exist as a +// draft standard at the time of PSO's development, but it wasn't widely used until over a decade later. IPv6 support +// is not implemented in any version of the PSO client that I've seen, but we implement Sylverant's version of this +// command anyway because newserv may connect to Sylverant via IPv6 when using the proxy. Sylverant sends the value 6 +// in header.flag in this case, presumably to indicate the protocol. struct S_ReconnectIPv6_Extension_19 { parray address; @@ -829,10 +738,9 @@ struct S_ReconnectIPv6_Extension_19 { le_uint16_t unused = 0; } __packed_ws__(S_ReconnectIPv6_Extension_19, 0x14); -// Because PSO PC and some versions of PSO DC/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. I first saw this technique -// used by Schthack; I don't know if it was his original creation. +// Because PSO PC and some versions of PSO DC/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. I first saw this technique used by +// Schthack; I don't know if it was his original creation. struct S_ReconnectSplit_19 { be_uint32_t pc_address = 0; @@ -848,21 +756,17 @@ struct S_ReconnectSplit_19 { // 1A (S->C): Large message box // Internal name: RcvText -// On V3, client will sometimes respond with a D6 command (see D6 for more -// information). -// Contents are plain text. 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. -// The maximum length of the message is 0x400 bytes. This is the only -// difference between this command and the D5 command (except on BB - see the -// notes on D5 for more information). +// On V3, client will sometimes respond with a D6 command (see D6 for more information). Contents are plain text. 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. +// The maximum length of the message is 0x400 bytes. This is the only difference between this command and the D5 +// command (except on BB - see the notes on D5 for more information). // 1B (S->C): Valid but ignored (all versions) // Internal name: RcvBattleData -// This command does nothing in all PSO versions. There is a SndBattleData -// string in the DC versions, but the corresponding function was deleted. +// This command does nothing in all PSO versions. There is a SndBattleData string in the DC versions, but the +// corresponding function was deleted. // 1C (S->C): Valid but ignored (all versions) // Internal name: RcvSystemFile @@ -870,34 +774,28 @@ struct S_ReconnectSplit_19 { // 1D: Ping // Internal name: RcvPing -// No arguments -// 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. +// No arguments. When sent to the client, the client will respond with a 1D command. Data sent by the server is +// ignored; the client always responds with a 1D command with no data. // 1E: Invalid command // 1F (C->S): Request information menu // Internal name: SndTextList -// No arguments -// This command is used in PSO DC and PC. It exists in V3 as well but is -// apparently unused. +// No arguments. This command is used in PSO DC and PC; it exists in V3 as well but is unused. // 1F (S->C): Information menu // Internal name: RcvTextList // Same format and usage as 07 command, except: // - The menu title will say "Information" instead of "Ship Select". -// - There is no way to request details before selecting a menu item (the client -// will not send 09 commands). -// - The player can press a button (B on GC, for example) to close the menu -// without selecting anything, unlike the ship select menu. The client does -// not send anything when this happens. +// - There is no way to request details before selecting a menu item (the client will not send 09 commands). +// - The player can press a button (B on GC, for example) to close the menu without selecting anything, unlike the ship +// select menu. The client does not send anything when this happens. // 20: Invalid command // 21: Invalid command -// My old notes call this command "GameGuard control (old versions of BB)", but -// it's not clear if this is accurate. At least, BB US v1.24.3 and later do not -// support this command. +// My old notes call this command "GameGuard control (old versions of BB)", but it's not clear if this is accurate. At +// least, BB US v1.24.3 and later do not support this command. // 0022: GameGuard challenge/response (BB) // This command is not valid on BB Trial Edition. @@ -907,26 +805,21 @@ struct SC_GameGuardCheck_BB_0022 { } __packed_ws__(SC_GameGuardCheck_BB_0022, 0x10); // 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. -// This command is not valid on BB Trial Edition. +// 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. This command is not valid on BB Trial Edition. +// header.flag is always 1. It may be that this is actually a more general "set cheating flag" command, but it's only +// used in the case described above; there are no other conditions that cause it to be sent. // 23 (S->C): Momoka Item Exchange result (BB) -// Sent in response to a 6xD9 command from the client. -// header.flag indicates if an item was exchanged: 0 means success, 1 means -// failure. -// This command is not valid on BB Trial Edition. +// Sent in response to a 6xD9 command from the client. header.flag indicates if an item was exchanged: 0 means success, +// 1 means failure. This command is not valid on BB Trial Edition. // 24 (S->C): Secret Lottery Ticket exchange result (BB) -// Sent in response to a 6xDE command from the client. -// The client sets 8 sequential quest registers, starting with start_reg_num, -// to the values specified in reg_values. Then it starts a new quest thread -// at the specified label. -// header.flag indicates whether the client had any Secret Lottery Tickets in -// their inventory (and hence could participate): 0 means success, 1 means -// failure. However, this value is unused by the client. +// Sent in response to a 6xDE command from the client. The client sets 8 sequential quest registers, starting with +// start_reg_num, to the values specified in reg_values. Then it starts a new quest thread at the specified label. +// header.flag indicates whether the client had any Secret Lottery Tickets in their inventory (and hence could +// participate): 0 means success, 1 means failure. However, this value is unused by the client. // This command is not valid on BB Trial Edition. struct S_ExchangeSecretLotteryTicketResult_BB_24 { @@ -937,9 +830,8 @@ struct S_ExchangeSecretLotteryTicketResult_BB_24 { } __packed_ws__(S_ExchangeSecretLotteryTicketResult_BB_24, 0x24); // 25 (S->C): Gallon's Plan result (BB) -// Sent in response to a 6xE1 command from the client. -// The client sets the quest registers reg_num1 to value1 and reg_num2 to -// value2, then starts a new quest thread at the specified label. +// Sent in response to a 6xE1 command from the client. The client sets the quest registers reg_num1 and reg_num2 to +// value1 and value2 respectively, then starts a new quest thread at the specified label. // This command is not valid on BB Trial Edition. struct S_GallonPlanResult_BB_25 { @@ -980,12 +872,11 @@ struct S_GallonPlanResult_BB_25 { // 40 (C->S): Guild card search // Internal name: SndFindUser -// There is an unused command named SndFavorite in the DC versions of PSO, -// which may have been related to this command. SndFavorite seems to be -// completely unused; its sender function was optimized out of all known -// builds, leaving only its name string remaining. -// The server should respond with a 41 command if the target is online. If the -// target is not online, the server doesn't respond at all. +// There is an unused command named SndFavorite in the DC versions of PSO, which may have been related to this command. +// SndFavorite seems to be completely unused; its sender function was optimized out of all known builds, leaving only +// its name string remaining. +// The server should respond with a 41 command if the target is online. If the target is not online, the server doesn't +// respond at all. struct C_GuildCardSearch_40 { le_uint32_t player_tag = 0x00010000; @@ -1003,15 +894,13 @@ struct S_GuildCardSearchResultT { le_uint32_t result_guild_card_number = 0; HeaderT reconnect_command_header; // Ignored by the client S_Reconnect_19 reconnect_command; - // The format of this string is "GAME-NAME,BLOCK##,SERVER-NAME". If the result - // player is not in a game, GAME-NAME should be the lobby name - for standard - // lobbies this is "BLOCK-"; for CARD lobbies this is + // The format of this string is "GAME-NAME,BLOCK##,SERVER-NAME". If the result player is not in a game, GAME-NAME + // should be the lobby name - for standard lobbies this is "BLOCK-"; for CARD lobbies this is // "BLOCK-C". pstring location_string; - // If the player chooses to meet the user, this extension data is sent in the - // login command (9D/9E) after connecting to the server designated in - // reconnect_command. When processing the 9D/9E, newserv uses only the - // lobby_id field within, but it fills in all fields when sending a 41. + // If the player chooses to meet the user, this extension data is sent in the login command (9D/9E) after connecting + // to the server designated in reconnect_command. When processing the 9D/9E, newserv uses only the lobby_id field + // within, but it fills in all fields when sending a 41. SC_MeetUserExtensionT extension; } __attribute__((packed)); using S_GuildCardSearchResult_PC_41 = S_GuildCardSearchResultT; @@ -1026,29 +915,26 @@ check_struct_size(S_GuildCardSearchResult_BB_41, 0x128); // 44 (S->C): Open file for download // Internal name: RcvDownLoadHead -// Used for downloading online quests. The client will react to a 44 command if -// the filename ends in .bin or .dat. -// This command is not implemented on DC NTE, so DC NTE cannot receive online -// quest files. -// For download quests (to be saved to the memory card) and GBA games, the A6 -// command is used instead. The client will react to A6 if the filename ends in -// .bin/.dat (quests), .pvr (textures), or .gba (GameBoy Advance games). -// It appears that the .gba handler for A6 was not deleted in PSO XB, even -// though it doesn't make sense for an XB client to receive such a file. +// Used for downloading online quests. The client will only react to a 44 command if the filename ends in .bin or .dat. +// This command is not implemented on DC NTE, so DC NTE cannot receive online quest files. +// For download quests (to be saved to the memory card) and GBA games, the A6 command is used instead. The client will +// react to A6 if the filename ends in .bin/.dat (quests), .pvr (textures), or .gba (GameBoy Advance games). It appears +// that the .gba handler for A6 was not deleted in PSO XB, even though it doesn't make sense for an XB client to +// receive such a file. +// header.flag has no meaning on the client, but the client will send the value back when acknowledging this command. struct S_OpenFile_DC_44_A6 { pstring name; // Should begin with "PSO/" - // The type field is only used for download quests (A6); it is ignored for - // online quests (44). The following values are valid for A6: + // The type field is only used for download quests (A6); it is ignored for online quests (44). The following values + // are valid for A6: // 0 = download quest (client expects .bin and .dat files) - // 1 = download quest (client expects .bin, .dat, and .pvr files) + // 1 = download quest with image (client expects .bin, .dat, and .pvr files) // 2 = GBA game (GC only; client expects .gba file only) // 3 = Episode 3 download quest (Ep3 only; client expects .bin file only) - // There is a bug in the type logic: an A6 command always overwrites the - // current download type even if the filename doesn't end in .bin, .dat, .pvr, - // or .gba. This may lead to a resource exhaustion bug if exploited carefully, - // but I haven't verified this. Generally the server should send all files for - // a given piece of content with the same type in each file's A6 command. + // There is a bug in the type logic: an A6 command always overwrites the current download type even if the filename + // doesn't end in .bin, .dat, .pvr, or .gba. This may lead to a resource exhaustion bug if exploited carefully, but I + // haven't verified this. Generally the server should send all files for a given piece of content with the same type + // in each file's A6 command. uint8_t type = 0; pstring filename; le_uint32_t file_size = 0; @@ -1061,9 +947,8 @@ struct S_OpenFile_PC_GC_44_A6 { le_uint32_t file_size = 0; } __packed_ws__(S_OpenFile_PC_GC_44_A6, 0x38); -// Curiously, PSO XB expects an extra 0x18 bytes at the end of this command, but -// those extra bytes are unused, and the client does not fail if they're -// omitted. +// Curiously, PSO XB expects an extra 0x18 bytes at the end of this command, but those extra bytes are unused, and the +// client does not fail if they're omitted. struct S_OpenFile_XB_44_A6 : S_OpenFile_PC_GC_44_A6 { pstring xb_filename; le_uint32_t content_meta; @@ -1080,10 +965,8 @@ struct S_OpenFile_BB_44_A6 { // 44 (C->S): Confirm open file (V3/BB) // Client sends this in response to each 44 sent by the server. +// header.flag contains the value that the server sent in its 44 command. -// header.flag = quest number (sort of - seems like the client just echoes -// whatever the server sent in its header.flag field. Also quest numbers can be -// > 0xFF so the flag is essentially meaningless) struct C_OpenFileConfirmation_44_A6 { pstring filename; } __packed_ws__(C_OpenFileConfirmation_44_A6, 0x10); @@ -1118,25 +1001,20 @@ struct C_OpenFileConfirmation_44_A6 { // 60: Broadcast command // Internal name: SndPsoData -// 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 or the subcommand index below for details on contents. -// The data in this command may be up to 0x400 bytes in length. If it's larger, -// the client will exhibit undefined behavior. +// When a client sends this, the server should forward it to all players in the same game/lobby, except the player who +// originally sent the command. See ReceiveSubcommands or the subcommand index below for details on contents. +// The data in this command may be up to 0x400 bytes in length. If it's larger, the client exhibits undefined behavior. // 61 (C->S): Player data // Internal name: SndCharaDataV2 (SndCharaData in DCv1) -// header.flag specifies the format version, which is related to (but not -// identical to) the game's major version. For example, the format version is 01 -// on DC v1, 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Episode 3. -// Upon joining a game, the client assigns inventory item IDs sequentially as -// (0x00010000 + (0x00200000 * lobby_client_id) + x). So, for example, player -// 3's 8th item's ID would become 0x00610007. The item IDs from the last game -// the player was in will appear in their inventory in this command. -// Note: If the client is in a game at the time this command is received, the -// inventory sent by the client only includes items that would not disappear if -// the client crashes! Essentially, it reflects the saved state of the player's -// character rather than the live state. +// header.flag specifies the format version, which is related to (but not identical to) the game's major version. For +// example, the format version is 01 on DC v1, 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Episode 3. +// Upon joining a game, the client assigns inventory item IDs as (0x00010000 + (0x00200000 * lobby_client_id) + x). So, +// for example, player 3's 8th item's ID would become 0x00610007. The item IDs from the last game the player was in +// will appear in their inventory in this command. +// Note: If the client is in a game at the time this command is received, the inventory sent by the client only +// includes items that would not disappear if the client crashes! Essentially, it reflects the saved state of the +// player's character rather than the live state. struct PlayerRecordsEntry_DC { /* 00 */ le_uint32_t client_id = 0; @@ -1187,9 +1065,8 @@ struct C_CharacterData_PC_61_98 { /* 0510 */ ChoiceSearchConfig choice_search_config; /* 0528 */ parray blocked_senders; /* 05A0 */ le_uint32_t auto_reply_enabled = 0; - // The auto-reply message can be up to 0x200 characters. If it's shorter than - // that, the client truncates the command after the first null value (rounded - // up to the next 4-byte boundary). + // The auto-reply message can be up to 0x200 characters. If it's shorter than that, the client truncates the command + // after the first null value (rounded up to the next 4-byte boundary). /* 05A4 */ // uint16_t auto_reply[...EOF]; } __packed_ws__(C_CharacterData_PC_61_98, 0x5A4); @@ -1200,9 +1077,8 @@ struct C_CharacterData_GCNTE_61_98 { /* 04D8 */ ChoiceSearchConfig choice_search_config; /* 04F0 */ parray blocked_senders; /* 0568 */ le_uint32_t auto_reply_enabled = 0; - // The auto-reply message can be up to 0x200 bytes. If it's shorter than that, - // the client truncates the command after the first zero byte (rounded up to - // the next 4-byte boundary). + // The auto-reply message can be up to 0x200 bytes. If it's shorter than that, the client truncates the command after + // the first zero byte (rounded up to the next 4-byte boundary). /* 056C */ // char auto_reply[...EOF]; } __packed_ws__(C_CharacterData_GCNTE_61_98, 0x56C); @@ -1214,9 +1090,8 @@ struct C_CharacterData_V3_61_98 { /* 0550 */ pstring info_board; /* 05FC */ parray blocked_senders; /* 0674 */ le_uint32_t auto_reply_enabled = 0; - // The auto-reply message can be up to 0x200 bytes. If it's shorter than that, - // the client truncates the command after the first zero byte (rounded up to - // the next 4-byte boundary). + // The auto-reply message can be up to 0x200 bytes. If it's shorter than that, the client truncates the command after + // the first zero byte (rounded up to the next 4-byte boundary). /* 0678 */ // char auto_reply[...EOF]; } __packed_ws__(C_CharacterData_V3_61_98, 0x678); @@ -1241,53 +1116,42 @@ struct C_CharacterData_BB_61_98 { /* 0650 */ pstring info_board; /* 07A8 */ parray blocked_senders; /* 0820 */ le_uint32_t auto_reply_enabled = 0; - // Like on V3, the client truncates the command if the auto reply message is - // shorter than 0x200 bytes. + // Like on V3, the client truncates the command if the auto reply message is shorter than 0x200 bytes. /* 0824 */ // uint16_t auto_reply[...EOF]; } __packed_ws__(C_CharacterData_BB_61_98, 0x824); // 62: Target command // Internal name: SndPsoData2 -// 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 or the subcommand index below for details on contents. -// The data in this command may be up to 0x400 bytes in length. If it's larger, -// the client will exhibit undefined behavior. +// 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 or the subcommand index below for details on contents. The data in this command may be up to +// 0x400 bytes in length. If it's larger, the client exhibits undefined behavior. // 63: Invalid command // 64 (S->C): Join game // Internal name: RcvStartGame3 -// This is sent to the joining player; the other players get a 65 instead. -// Note that (except on Episode 3) this command does not include the player's -// disp or inventory data. The clients in the game are responsible for sending -// that data to each other during the join process with 60/62/6C/6D commands. +// This is sent to the joining player; the other players get a 65 instead. Note that (except on Episode 3) this command +// does not include the player's disp or inventory data. The clients in the game are responsible for sending that data +// to each other during the join process with 60/62/6C/6D commands. -// After receiving a 64 command, the client starts the game loading procedure, -// during which it will completely ignore other 64 or 65 commands, and will -// delay processing of all other commands except 1D until the loading procedure -// is done. If more than 0x10000 bytes of commands are sent during loading, any -// commands that don't fit in the buffer are lost. +// After receiving a 64 command, the client starts the game loading procedure, during which it will completely ignore +// other 64 or 65 commands, and will delay processing of all other commands except 1D until loading is done. If more +// than 0x10000 bytes of commands are sent during loading, any commands that don't fit in the buffer are lost. -// Curiously, this command is named RcvStartGame3 internally, while 0E is named -// RcvStartGame. The string RcvStartGame2 appears in the DC versions, but it -// seems the relevant code was deleted - there are no references to the string. -// Based on the large gap between commands 0E and 64, we can't guess at which -// command number RcvStartGame2 might have been. +// Curiously, this command is named RcvStartGame3 internally, while 0E is named RcvStartGame. The string RcvStartGame2 +// appears in the DC versions, but it seems the relevant code was deleted - there are no references to the string. // Header flag = entry count template struct S_JoinGameT_DC_PC { - // Note: It seems Sega servers sent uninitialized memory in the variations - // field when sending this command to start an Episode 3 tournament game. This - // can be misleading when reading old logs from those days, but the Episode 3 + // Note: It seems Sega servers sent uninitialized memory in the variations field when sending this command to start + // an Episode 3 tournament game. This can be misleading when reading old logs from those days, but the Episode 3 // client really does ignore it. /* 0004 */ Variations variations; - // Unlike lobby join commands, these are filled in in their slot positions. - // 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. + // Unlike lobby join commands, these are filled in in their slot positions. 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. /* 0084 */ parray lobby_data; /* 0104 */ uint8_t client_id = 0; /* 0105 */ uint8_t leader_id = 0; @@ -1320,9 +1184,8 @@ struct S_JoinGame_GC_64 : S_JoinGameT_DC_PC { } __packed_ws__(S_JoinGame_GC_64, 0x110); struct S_JoinGame_Ep3_64 : S_JoinGame_GC_64 { - // This field is only present if the game (and client) is Episode 3. Similarly - // to lobby_data in the base struct, 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 in the base struct, all + // four of these are always present and they are filled in in slot positions. struct Ep3PlayerEntry { PlayerInventory inventory; PlayerDispDataDCPCV3 disp; @@ -1350,21 +1213,18 @@ struct S_JoinGame_BB_64 : S_JoinGameT_DC_PC { uint8_t episode = 0; uint8_t unused = 0; // Corresponds to xb_enable_voice_chat; unused on BB uint8_t solo_mode = 0; - // This flag should be nonzero if the joining client will load a quest - // immediately after joining. The only tangible effect of this flag being - // enabled is that the game won't delete all active telepipes during quest - // loading. + // This flag should be nonzero if the joining client will load a quest immediately after joining. The only tangible + // effect of this flag being enabled is that the game won't delete all active telepipes during quest loading. uint8_t is_in_quest = 0; } __packed_ws__(S_JoinGame_BB_64, 0x1A0); // 65 (S->C): Add player to game // Internal name: RcvBurstGame -// When a player joins an existing game, the joining player receives a 64 -// command (described above), and the players already in the game receive a 65 -// command containing only the joining player's data. +// When a player joins an existing game, the joining player receives a 64 command (described above), and the players +// already in the game receive a 65 command containing only the joining player's data. -// Similarly to 64, the client will ignore 64 and 65 commands while loading, -// and will buffer all other commands except 1D until loading is done. +// Similarly to 64, the client will ignore 64 and 65 commands while loading, and will buffer all other commands except +// 1D until loading is done. struct LobbyFlags_DCNTE { uint8_t client_id = 0; @@ -1379,9 +1239,8 @@ struct LobbyFlags { uint8_t disable_udp = 1; uint8_t lobby_number = 0; uint8_t block_number = 0; - // When this flag is set in the lobby join commands (67/68), the client will - // show the Battle option in the game creation menu. This only has an effect - // on DCv1. On 11/2000, the option always appears but does nothing, and on + // When this flag is set in the lobby join commands (67/68), the client will show the Battle option in the game + // creation menu. This only has an effect on DCv1. On 11/2000, the option always appears but does nothing, and on // DCv2 and later, the game mode option is always present. uint8_t enable_battle_mode_v1 = 1; uint8_t event = 0; @@ -1398,8 +1257,8 @@ struct S_JoinLobbyT { PlayerInventory inventory; DispDataT disp; } __attribute__((packed)); - // 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) + // 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) parray entries; static inline size_t size(size_t used_entries) { @@ -1423,8 +1282,8 @@ struct S_JoinLobby_XB_65_67_68 { PlayerInventory inventory; PlayerDispDataDCPCV3 disp; } __packed_ws__(Entry, 0x468); - // 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) + // 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) parray entries; static inline size_t size(size_t used_entries) { @@ -1434,14 +1293,13 @@ struct S_JoinLobby_XB_65_67_68 { // 66 (S->C): Remove player from game // Internal name: RcvExitGame -// This is sent to all players in a game except the leaving player. -// header.flag should be set to the leaving player ID (same as client_id). +// This is sent to all remaining players in a game when a player leaves. header.flag is the leaving client ID (same as +// client_id). struct S_LeaveLobby_66_69_Ep3_E9 { uint8_t client_id = 0; uint8_t leader_id = 0; - // Note: disable_udp only has an effect for games; it is unused for lobbies - // and spectator teams. + // Note: disable_udp only has an effect for games; it is unused for lobbies and spectator teams. uint8_t disable_udp = 1; uint8_t unused = 0; } __packed_ws__(S_LeaveLobby_66_69_Ep3_E9, 4); @@ -1451,39 +1309,36 @@ struct S_LeaveLobby_66_69_Ep3_E9 { // This is sent to the joining player; the other players receive a 68 instead. // Same format as 65 command, but used for lobbies instead of games. -// Curiously, this command is named RcvStartLobby2 internally, but there is no -// command named RcvStartLobby. The string "RcvStartLobby" does appear in the DC -// game executable, but it appears the relevant code was deleted. +// Curiously, this command is named RcvStartLobby2 internally, but there is no command named RcvStartLobby. The string +// "RcvStartLobby" does appear in the DC game executable, but it appears the relevant code was deleted. // 68 (S->C): Add player to lobby // Internal name: RcvBurstLobby -// Same format as 65 command, but used for lobbies instead of games. -// The command only includes the joining player's data. +// Same format as 65 command, but used for lobbies instead. The command only includes the joining player's data. // 69 (S->C): Remove player from lobby // Internal name: RcvExitLobby -// Same format as 66 command, but used for lobbies instead of games. +// Same format as 66 command, but used for lobbies instead. // 6A: Invalid command // 6B: Invalid command -// 6C: Broadcast command +// 6C: Large broadcast command // Internal name: RcvPsoDataLong and SndPsoDataLong -// Same format and usage as 60 command, but with no size limit. +// Same format and usage as 60 command, but with no 0x400-byte size limit. -// 6D: Target command +// 6D: Large target command // Internal name: RcvPsoDataLong and SndPsoDataLong2 -// Same format and usage as 62 command, but with no size limit. +// Same format and usage as 62 command, but with no 0x400-byte size limit. // 6E: Invalid command // 6F (C->S): Done loading // Internal name: SndBurstEnd -// This command is sent when a player is done loading and other players can then -// join the game. On BB, this command is sent a 006F after loading into a game, -// or as 016F after loading a joinable quest. (This means when a BB client joins -// a game with a quest in progress, they will send 006F when they're ready to -// receive the quest files, and 016F when they're actually ready to play.) +// This command is sent when a player is done loading and other players can then join the game. On BB, this command +// is sent a 006F after loading into a game, or as 016F after loading a joinable quest. (This means when a BB client +// joins a game with a quest in progress, they will send 006F when they're ready to receive the quest files, and 016F +// when they're actually ready to play.) // 70: Invalid command // 71: Invalid command @@ -1504,11 +1359,9 @@ struct S_LeaveLobby_66_69_Ep3_E9 { // 80: Valid but ignored (all versions except BB) // Internal names: RcvGenerateID and SndGenerateID -// This command appears to be used to set the next item ID for the given player -// slot. PSO V3 and later accept this command, but ignore it entirely. Notably, -// no version of PSO except for DC NTE ever sends this command - it's likely it -// was used to implement some item ID sync semantics that were later changed to -// use the leader as the source of truth. +// This command appears to be used to set the next item ID for the given player slot. PSO V3 and later accept this +// command, but ignore it entirely. Notably, no version of PSO except for DC NTE ever sends this command - it's likely +// it was used to implement some item ID sync semantics that were later changed to make the leader the source of truth. struct C_GenerateID_DCNTE_80 { le_uint32_t id = 0; @@ -1526,19 +1379,15 @@ struct S_GenerateID_DC_PC_V3_80 { // 81: Simple mail // Internal name: RcvChatMessage and SndChatMessage -// Format is the same in both directions. The server should forward the command -// to the player with to_guild_card_number, if they are online. If they are not -// online, the server may store it for later delivery, send their auto-reply -// message back to the original sender, or simply drop the message. -// On GC (and probably other versions too) the unused space after the text -// contains uninitialized memory when the client sends this command. newserv -// clears the uninitialized data for security reasons before forwarding. -// The maximum length of the message is 170 characters, despite the field being -// able to hold triple that amount. +// The server should forward the command to the player with to_guild_card_number, if they are online. If they are not +// online, the server may store it for later delivery, send their auto-reply message back to the original sender, or +// simply drop the message. +// On GC (and probably other versions too) the unused space after the text contains uninitialized memory when the +// client sends this command. newserv clears the uninitialized data for security reasons before forwarding. +// The maximum length of the message is 170 characters, despite the field being able to hold triple that amount. struct SC_SimpleMail_PC_81 { - // If player_tag and from_guild_card_number are zero, the message cannot be - // replied to. + // If player_tag and from_guild_card_number are zero, the message cannot be replied to. le_uint32_t player_tag = 0x00010000; le_uint32_t from_guild_card_number = 0; pstring from_name; @@ -1574,26 +1423,21 @@ struct SC_SimpleMail_BB_81 { // 83 (S->C): Lobby menu // Internal name: RcvRoomInfo -// Curiously, there is a SndRoomInfo string in the DC versions. Perhaps in an -// early (pre-NTE) build, the client had to request the lobby menu from the -// server, and SndRoomInfo was the command to do so. The code to send this -// command must have been removed before DC NTE. -// This command sets the menu item IDs that the client uses for the lobby -// teleporter menu. On DCv1, the client expects 10 entries here; on all other -// versions except Episode 3, the client expects 15 items here; on Episode 3, -// the client expects 20 items here. 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. On Episode 3, the CARD lobbies are the last five entries, even -// though they appear at the top of the list on the player's screen. +// Curiously, there is a SndRoomInfo string in the DC versions. Perhaps in an early (pre-NTE) build, the client had to +// request the lobby menu from the server, and SndRoomInfo was the command to do so. The code to send this command must +// have been removed before DC NTE. +// This command sets the menu item IDs that the client uses for the lobby teleporter menu. On DCv1, the client expects +// 10 entries here; on all other versions except Episode 3, the client expects 15 items here; on Episode 3, the client +// expects 20 items here. 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. On Episode 3, the CARD lobbies are the last five +// entries, even though they appear at the top of the list on the player's screen. -// Command is a list of these; header.flag is the entry count (10, 15 or 20) +// Command is a list of these; header.flag is the entry count (10, 15 or 20, as described above) struct S_LobbyListEntry_83 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; - // It appears that Sega's servers sent the number of players in each lobby in - // this field, but the client ignores it. + // It appears that Sega's servers sent the number of players in each lobby in this field, but the client ignores it. le_uint32_t player_count = 0; } __packed_ws__(S_LobbyListEntry_83, 0x0C); @@ -1618,43 +1462,38 @@ struct C_Login_DCNTE_88 { } __packed_ws__(C_Login_DCNTE_88, 0x22); // 88 (S->C): Account check result (DC NTE only) -// No arguments except header.flag. -// If header.flag is zero, client will respond with an 8A command. Otherwise, it -// will respond with an 8B command. This is the same behavior as for the 18 -// command (and in fact, the client handler is shared between both commands.) +// No arguments except header.flag. If header.flag is zero, client will respond with an 8A command. Otherwise, it will +// respond with an 8B command. This is the same behavior as for the 18 command (and in fact, the client handler is +// shared between both commands.) // 88 (S->C): Update lobby arrows (except DC NTE) -// If this command is sent while a client is joining a lobby, the client may -// ignore it. For this reason, the server should wait a few seconds after a -// client joins a lobby before sending an 88 command. +// If this command is sent while a client is joining a lobby, the client may ignore it. For this reason, the server +// should wait a few seconds after a client joins a lobby before sending an 88 command. // This command is not supported on DC v1. -// 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. +// 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 = 0x00010000; le_uint32_t guild_card_number = 0; // Values for arrow_color: - // any number not specified below (including 00) - none - // 01 - red - // 02 - blue - // 03 - green - // 04 - yellow - // 05 - purple - // 06 - cyan - // 07 - orange - // 08 - pink - // 09 - white - // 0A - white - // 0B - white - // 0C - black + // any number not specified below (including 00) - none + // 01 - red + // 02 - blue + // 03 - green + // 04 - yellow + // 05 - purple + // 06 - cyan + // 07 - orange + // 08 - pink + // 09, 0A, or 0B - white + // 0C - black le_uint32_t arrow_color = 0; } __packed_ws__(S_ArrowUpdateEntry_88, 0x0C); // 89 (C->S): Set lobby arrow // header.flag = arrow color number (see above); no other arguments. -// Server should send an 88 command to all players in the lobby. +// Upon receipt, the server should send an 88 command to all players in the lobby. // 89 (S->C): Start encryption at login server (DC NTE) // Behaves exactly the same as the 17 command. @@ -1672,21 +1511,18 @@ struct C_ConnectionInfo_DCNTE_8A { } __packed_ws__(C_ConnectionInfo_DCNTE_8A, 0xA0); // 8A (S->C): Connection information result (DC NTE only) -// header.flag is a success flag. If 0 is sent, the client shows an error -// message and disconnects. Otherwise, the client responds with an 8B command. +// header.flag is a success flag. If it's zero, the client shows an error message and disconnects. Otherwise, the +// client responds with an 8B command. // 8A (C->S): Request lobby/game name (except DC NTE) // No arguments // 8A (S->C): Lobby/game name (except DC NTE) -// Contents is a string containing the lobby or game name. All versions after -// DCv1 (including the August 2001 DCv2 prototype) send an 8A command to request -// the team name after joining a game. The response is used to handle the -// team_name token in quest strings, and appears in some Challenge Mode -// information windows. -// Even though this was only ever used to retrieve the game name, Sega's -// original servers also replied to 8A if it was sent in a lobby. They would -// return a string like "LOBBY01" in that case. +// Contents is a string containing the lobby or game name. All versions after DCv1 send an 8A command to request the +// team name after joining a game. The response is used to handle the team_name token in quest strings, and appears in +// some Challenge Mode information windows. +// Even though this was only ever used to retrieve the game name, Sega's original servers also replied to 8A if it was +// sent in a lobby. They would return a string like "LOBBY01" in that case. // 8B: Log in (DC NTE only) @@ -1713,8 +1549,7 @@ struct C_LoginExtended_DCNTE_8B : C_Login_DCNTE_8B { // 8C: Invalid command // 8D (S->C): Request player data (DC NTE only) -// Behaves the same as 95 (S->C) on all other versions, but DC NTE crashes if it -// receives 95. +// Behaves the same as 95 (S->C) on all other versions, but DC NTE crashes if it receives 95. // 8E: Ship select menu (DC NTE) // Behaves exactly the same as the A0 command (in both directions). @@ -1723,18 +1558,14 @@ struct C_LoginExtended_DCNTE_8B : C_Login_DCNTE_8B { // Behaves exactly the same as the A1 command (in both directions). // 90 (C->S): V1 login (DC/PC/V3) -// This command is used during the DCv1 login sequence; a DCv1 client will -// respond to a 17 command with an (encrypted) 90. If a V3 client receives a 91 -// command, however, it will also send a 90 in response, though the contents -// will be blank (all zeroes). +// This command is used during the DCv1 login sequence; a DCv1 client will respond to a 17 command with 90. If a V3 +// client receives a 91 command, however, it will also send a 90 in response, though the contents will be blank. struct C_LoginV1_DC_PC_V3_90 { pstring serial_number; pstring access_key; - // Note: There is a bug in the Japanese and prototype versions of DCv1 that - // cause the client to send this command despite its size not being a - // multiple of 4. This is fixed in later versions, so we handle both cases in - // the receive handler. + // Note: There is a bug in the Japanese and prototype versions of DCv1 that cause the client to send this command + // despite its size not being a multiple of 4. This is fixed in later versions, so we have to handle both cases. } __packed_ws__(C_LoginV1_DC_PC_V3_90, 0x22); // 90 (S->C): Account verification result (V3) @@ -1742,14 +1573,11 @@ struct C_LoginV1_DC_PC_V3_90 { // 91 (S->C): Start encryption at login server (legacy; non-BB only) // Internal name: RcvPsoRegistConnect -// Same format and usage as 17 command, except the client will respond with a 90 -// command. On versions that support it, this is strictly less useful than the -// 17 command. Curiously, this command appears to have been implemented after -// the 17 command since it's missing from the DC NTE version, but the 17 command -// is named RcvPsoRegistConnectV2 whereas 91 is simply RcvPsoRegistConnect. It's -// likely that after DC NTE, Sega simply changed the command numbers for this -// group of commands from 88-8F to 90-A1 (so DC NTE's 89 command became the 91 -// command in all later versions). +// Same format and usage as 17 command, except the client will respond with a 90 command. On versions that support it, +// this is strictly less useful than the 17 command. Curiously, this command appears to have been implemented after the +// 17 command since it's missing from the DC NTE version, but the 17 command is named RcvPsoRegistConnectV2 whereas 91 +// is simply RcvPsoRegistConnect. It's likely that after DC NTE, Sega simply changed the command numbers for this group +// of commands from 88-8F to 90-A1 (so DC NTE's 89 command became the 91 command in all later versions). // 92 (C->S): Register (DC) @@ -1800,39 +1628,35 @@ struct C_LoginBase_BB_93 { /* 0C */ Language language = Language::JAPANESE; /* 0D */ int8_t character_slot = 0; // 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 - // 06 - lobby server (with Meet User fields specified) + // 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 + // 06 - lobby server (with Meet User fields specified) /* 0E */ uint8_t connection_phase = 0; /* 0F */ uint8_t client_code = 0; /* 10 */ le_uint32_t security_token = 0; /* 14 */ pstring username; /* 44 */ pstring password; - - // These fields map to the same fields in SC_MeetUserExtensionT. There is no - // equivalent of the name field from that structure on BB (though newserv - // doesn't use it anyway). + // These fields map to the same fields in SC_MeetUserExtensionT. There is no equivalent of the name field from that + // structure on BB (though newserv doesn't use it anyway). /* 74 */ le_uint32_t menu_id = 0; /* 78 */ le_uint32_t preferred_lobby_id = 0; /* 7C */ } __packed_ws__(C_LoginBase_BB_93, 0x7C); struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 { - // Note: Unlike other versions, BB puts the version string in the client - // config at connect time. So the first time the server gets this command, it - // will be something like "Ver. 1.24.3". This format is used on older client + // Note: Unlike other versions, BB puts the version string in the client config at connect time. So the first time + // the server gets this command, it will be something like "Ver. 1.24.3". This format is used on older client // versions (before 1.23.8?) /* 7C */ parray client_config; /* A4 */ } __packed_ws__(C_LoginWithoutHardwareInfo_BB_93, 0xA4); struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 { - // See the comment in the above structure. This format is used on newer client - // versions. + // See the comment in the above structure. This format is used on newer client versions. /* 7C */ be_uint64_t hardware_id; /* 84 */ parray client_config; /* AC */ @@ -1843,66 +1667,53 @@ struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 { // 95 (S->C): Request player data // Internal name: RcvRecognition // No arguments -// For some reason, some servers send high values in the header.flag field here. -// The header.flag field is completely unused by the client, however - 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. +// For some reason, some servers send high values in the header.flag field here. The header.flag field is completely +// unused by the client, however - 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. // Client will respond with a 61 command. // 96 (C->S): Character save information // Internal name: SndSaveCountCheck struct C_CharSaveInfo_DCv2_PC_V3_BB_96 { - // The creation timestamp is the number of seconds since 12:00AM on 1 January - // 2000. Instead of computing this directly from the TBR (on PSO GC), the game - // uses localtime(), then converts that to the desired timestamp. The leap - // year correction in the latter phase of this computation seems incorrect; it - // adds a day in 2002, 2006, etc. instead of 2004, 2008, etc. See - // compute_psogc_timestamp in SaveFileFormats.cc for details. + // The creation timestamp is the number of seconds since 12:00AM on 1 January 2000. Instead of computing this + // directly from the TBR (on PSO GC), the game uses localtime(), then converts that to the desired timestamp. The + // leap year correction in the latter phase of this computation seems incorrect; it adds a day in 2002, 2006, etc. + // instead of 2004, 2008, etc. See compute_psogc_timestamp in SaveFileFormats.cc for details. le_uint32_t creation_timestamp = 0; - // This field counts certain events on a per-character basis. One of the - // relevant events is the act of sending a 96 command; another is the act of - // receiving a 97 command (to which the client responds with a B1 command). - // Presumably Sega's original implementation could keep track of this value - // for each character and could therefore tell if a character had connected to - // an unofficial server between connections to Sega's servers. + // This field counts certain events on a per-character basis. One of the relevant events is the act of sending a 96 + // command; another is the act of receiving a 97 command (to which the client responds with a B1 command). Presumably + // Sega's original implementation could keep track of this value for each character and could therefore tell if a + // character had connected to an unofficial server between connections to Sega's servers. le_uint32_t event_counter = 0; } __packed_ws__(C_CharSaveInfo_DCv2_PC_V3_BB_96, 8); // 97 (S->C): Save to memory card // Internal name: RcvSaveCountCheck // No arguments -// Internally, this command is called RcvSaveCountCheck, even though the counter -// in the 96 command (to which 97 is a reply) counts more events than saves. -// Sending this command with header.flag == 0 will show a message saying that -// "character data was improperly saved", and will delete the character's items -// and challenge mode records. newserv (and all other unofficial servers) always -// send this command with flag == 1, which causes the client to save normally. -// If a PSO PC client receives this command multiple times during a session, the -// player will see the "character data may be damaged" message and be asked if -// they want to restore the pre-session backup data. +// Internally, this command is called RcvSaveCountCheck, even though the counter in the 96 command (to which 97 is a +// reply) counts more events than saves. Sending this command with header.flag == 0 will show a message saying that +// "character data was improperly saved", and will delete the character's items and challenge mode records. newserv +// always sends this command with flag == 1, which causes the client to save normally. +// If a PSO PC client receives this command multiple times during a session, the player will see the "character data +// may be damaged" message and be asked if they want to restore the pre-session backup data. // Client will respond with a B1 command if header.flag is nonzero. // 98 (C->S): Leave game // Internal name: SndUpdateCharaDataV2 (SndUpdateCharaData in DCv1) -// Same format as 61 command. The server should update its view of the client's -// player data and remove the client from the game it's in (if any), but should -// NOT assign it to an available lobby. The client will send an 84 when it's +// Same format as 61 command. The server should update its view of the client's player data and remove the client from +// the game it's in (if any), but should NOT assign it to an available lobby. The client will send an 84 when it's // ready to join a lobby. // Similarly to 64 and 65, the client will ignore 64 and 65 commands while -// loading the lobby after sending 98, and will buffer all other commands -// except 1D until loading is done. +// loading the lobby after sending 98, and will buffer all other commands except 1D until loading is done. // 99 (C->S): Server time accepted // Internal name: SndPsoDirList // No arguments -// This command's internal name suggests that it's actually a request for the -// ship select menu, but it's only sent as the response to a B1 command (server -// time) and the client doesn't set any state to indicate it's waiting for a -// ship select menu, so we just treat it as confirmation of a received B1 -// command instead. +// This command's internal name suggests that it's actually a request for the ship select menu, but it's only sent as +// the response to a B1 command (server time) and the client doesn't set any state to indicate it's waiting for a ship +// select menu, so we just treat it as confirmation of a received B1 command instead. // 9A (C->S): Initial login (no password or client config) // Internal name: RcvPsoRegistCheck @@ -1923,39 +1734,35 @@ struct C_Login_DC_PC_V3_9A { // 9A (S->C): Account verification result // Internal name: RcvPsoRegistCheckV2 -// The result code is sent in the header.flag field. Result codes: -// 00 = license ok (don't save to memory card; client responds with 9D/9E) -// 01 = registration required (client responds with a 9C command) -// 02 = license ok (save to memory card; client responds with 9D/9E) -// For all of the below cases, the client doesn't respond and displays a message -// box describing the error. When the player presses a button, the client then -// disconnects. -// 03 = access key invalid (125) (client deletes saved license info) -// 04 = serial number invalid (126) (client deletes saved license info) -// 07 = invalid Hunter's License (117) -// 08 = Hunter's License expired (116) -// 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) -// 0E = connection error (115) -// 0F = connection suspended (111) -// 10 = connection suspended (111) -// 11 = Hunter's License expired (116) -// 12 = invalid Hunter's License (117) -// 13 = servers under maintenance (118) +// The result code is sent in the header.flag field. Success result codes: +// 00 = license ok (don't save to memory card; client responds with 9D/9E) +// 01 = registration required (client responds with a 9C command) +// 02 = license ok (save to memory card; client responds with 9D/9E) +// For all of the below cases, the client doesn't respond and displays a message box describing the error. When the +// player presses a button, the client then disconnects. +// 03 = access key invalid (125) (client deletes saved license info) +// 04 = serial number invalid (126) (client deletes saved license info) +// 07 = invalid Hunter's License (117) +// 08 = Hunter's License expired (116) +// 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) +// 0E = connection error (115) +// 0F = connection suspended (111) +// 10 = connection suspended (111) +// 11 = Hunter's License expired (116) +// 12 = invalid Hunter's License (117) +// 13 = servers under maintenance (118) // Seems like most (all?) of the rest of the codes are "network error" (119). // 9B (S->C): Secondary server init (non-BB, non-DCv1) // Behaves exactly the same as 17 (S->C). // 9B (S->C): Secondary server init (BB) -// Format is the same as 03 (and the client uses the same encryption afterward). -// The only differences that 9B has from 03: -// - 9B does not work during the data-server phase (before the client has -// reached the ship select menu), whereas 03 does. -// - For command 9B, the copyright string must be -// "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.". -// - The client will respond with a command DB instead of a command 93. +// Format is the same as 03 (and the client uses the same encryption afterward). The only differences: +// - 9B does not work during the data-server phase (before the client has reached the ship select menu), but 03 does. +// - For command 9B, the copyright string must be "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.". +// - The client will respond to 9B with DB instead of 93. // 9C (C->S): Register // Internal name: SndPsoRegist @@ -1985,33 +1792,29 @@ struct C_Register_BB_9C { // 9C (S->C): Register result // Internal name: RcvPsoRegistV2 -// On GC, the only possible error here seems to be wrong password (127) which is -// displayed if the header.flag field is zero. On DCv2/PC, the error text says -// something like "registration failed" instead. If header.flag is nonzero, the +// On GC, the only possible error here seems to be wrong password (127) which is displayed if the header.flag field is +// zero. On DCv2/PC, the error text says something like "registration failed" instead. If header.flag is nonzero, the // client proceeds with the login procedure by sending a 9D or 9E. // 9D (C->S): Log in without client config (DCv2/PC/GC) // Not used on DCv1 - that version uses 93 instead. -// Not used on most versions of V3 - the client sends 9E instead. The one -// type of PSO V3 that uses 9D is the Trial Edition of Episodes 1&2. -// The extended version of this command is sent if the client has not yet -// received an 04 (in which case the extended fields are blank) or if the client -// selected the Meet User option, in which case it specifies the requested lobby -// by its menu ID and item ID. +// Not used on most versions of V3 - the client sends 9E instead. The one type of PSO V3 that uses 9D is the Trial +// Edition of Episodes 1&2. +// The extended version of this command is sent if the client has not yet received an 04 (in which case the extended +// fields are blank) or if the client selected the Meet User option, in which case it specifies the requested lobby by +// its menu ID and item ID. struct C_Login_DC_PC_GC_9D { /* 00 */ le_uint32_t player_tag = 0x00010000; // 0x00010000 if guild card is set (via 04) /* 04 */ le_uint32_t guild_card_number = 0; // 0xFFFFFFFF if not set // The hardware ID is different for various PSO versions: - // - All DC versions: the hardware ID comes from the FUNC_SYSINFO_ID syscall - // (as KallistiOS refers to it), which returns a 64-bit integer. PSO uses - // the low 48 bits; the high 16 bits are masked out and are always zero. - // - PC V2: the hardware ID is always 0000FFFFFFFFFFFF - // - GC NTE: the last byte of the hardware ID is uninitialized memory from - // the TProtocol constructor's stack; the other bytes are all zeroes. + // - All DC versions: the hardware ID comes from the FUNC_SYSINFO_ID syscall (as KallistiOS refers to it), which + // returns a 64-bit integer. PSO uses the low 48 bits; the high 16 bits are masked out and are always zero. + // - PC V2: the hardware ID is always 0000FFFFFFFFFFFF. + // - GC NTE: the last byte of the hardware ID is uninitialized memory from the TProtocol constructor's stack; the + // other bytes are all zeroes. // - V3: the hardware ID is all zeroes. - // On the client, this is actually an array of 8 bytes, but we treat it as a - // single integer for simplicity. + // On the client, this is actually an array of 8 bytes, but we treat it as a single integer for simplicity. /* 08 */ be_uint64_t hardware_id; /* 10 */ le_uint32_t sub_version = 0; /* 14 */ uint8_t is_extended = 0; // If 1, structure has extended format @@ -2036,13 +1839,12 @@ struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D { } __packed_ws__(C_LoginExtended_PC_9D, 0x14C); // 9E (C->S): Log in with client config (PC/V3/BB) -// Not used on GC Episodes 1&2 Trial Edition, nor on v1 or most v2 versions. -// Of all pre-v3 versions, only the latest version of PCv2 appears to use this. -// The extended version of this command is used in the same circumstances as -// when PSO PC uses the extended version of the 9D command. -// PSO XB does not send the client config (security data) in the 9E command, -// even though there is a space for it. The server must use 9F instead to -// retrieve the client config. +// Not used on GC Episodes 1&2 Trial Edition, nor on v1 or most v2 versions. Of all pre-v3 versions, only the latest +// version of PCv2 appears to use this. +// The extended version of this command is used in the same circumstances as when PSO PC uses the extended version of +// the 9D command. +// PSO Xbox does not send the client config (security data) in the 9E command, even though there is a space for it. The +// server must use 9F instead to retrieve the client config. // header.flag is 1 if the client has UDP disabled. struct C_Login_PC_GC_9E : C_Login_DC_PC_GC_9D { @@ -2058,8 +1860,7 @@ struct C_LoginExtended_GC_9E : C_Login_PC_GC_9E { struct C_Login_XB_9E : C_Login_DC_PC_GC_9D { /* 00C8 */ parray unknown_a4; /* 00E8 */ XBNetworkLocation xb_netloc; - // By default, this array is initialized with [0x5A2773DD, 0xD82B2345, - // 0x4FF904D5] on 4OEU (US TU version). + // By default, this array is initialized with [0x5A2773DD, 0xD82B2345, 0x4FF904D5] on 4OEU (US TU version). /* 0118 */ parray xb_unknown_a1a; /* 0124 */ le_uint32_t xb_user_id_high = 0; /* 0128 */ le_uint32_t xb_user_id_low = 0; @@ -2090,10 +1891,9 @@ struct C_LoginExtended_BB_9E { } __packed_ws__(C_LoginExtended_BB_9E, 0x170); // 9F (S->C): Request client config / security data (PC/V3/BB) -// This command is not valid on PSO GC Episodes 1&2 Trial Edition nor on any -// other pre-v3 versions, except the latest PC v2 version, which does have it. -// Client will respond with a 9F command. -// No arguments +// This command is not valid on PSO GC Episodes 1&2 Trial Edition nor on any other pre-v3 versions, except the latest +// PC v2 version, which does have it. +// No arguments. Client will respond with a 9F command. // 9F (C->S): Client config / security data response (PC/V3/BB) // The data is opaque to the client, as described at the top of this file. @@ -2131,29 +1931,23 @@ struct C_ChangeShipOrBlock_A0_A1 { // 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 +// 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_QuestMenuEntryT { - // Note: The game treats menu_id as two 8-bit fields followed by a 16-bit - // field. In most situations, this is opaque to the server, so we treat it as - // a single 32-bit field; however, in the case of the quest menu, the first - // and second bytes have meaning on the client. + // Note: The game treats menu_id as two 8-bit fields followed by a 16-bit field. In most situations, this is opaque + // to the server, so we treat it as a single 32-bit field; however, in the case of the quest menu, the first and + // second bytes have meaning on the client. // - // The first byte is used as the quest episode number, which is only relevant - // for showing the Challenge Mode times window when a quest is selected. - // This byte must be set correctly on the quest category entry, not the quest - // itself, so the Episode 1 Challenge quests category should have a value of - // 1 in this byte, and the Episode 2 Challenge quests category should have a - // value of 2. (This is not the only condition required for the Challenge + // The first byte is used as the quest episode number, which is only relevant for showing the Challenge Mode times + // window when a quest is selected. This byte must be set correctly on the quest category entry, not the quest + // itself, so the Episode 1 Challenge quests category should have a value of 1 in this byte, and the Episode 2 + // Challenge quests category should have a value of 2. (This is not the only condition required for the Challenge // Mode times window to work; see the description of command A3 also.) // - // The second byte of the menu ID is used to determine which icon appears to - // the left of the quest name. - // Specifically: + // The second byte of the menu ID is used to determine which icon appears to the left of the quest name: // 0 = online quest icon (green diamond) // 1 = download quest icon (green square with outlined diamond) // 2 = completed download quest icon (orange square with outlined diamond) @@ -2175,66 +1969,52 @@ struct S_QuestMenuEntry_BB_A2_A4 { le_uint32_t item_id = 0; pstring name; pstring short_description; - // If this field is set, a yellow hex icon is displayed instead of the green - // or orange diamond icon, and the quest is grayed out and cannot be selected. - // This field is ignored if the icon type (see S_QuestMenuEntry) isn't 1 or 2. + // If this field is set, a yellow hex icon is displayed instead of the green or orange diamond icon, and the quest is + // grayed out and cannot be selected. This field is ignored if the icon type (see S_QuestMenuEntry) isn't 1 or 2. uint8_t disabled = 0; parray unused; } __packed_ws__(S_QuestMenuEntry_BB_A2_A4, 0x13C); // A3 (S->C): Quest information -// Same format as 1A/D5 command (plain text). The header.flag field is used to -// inform the client of the Challenge stage number, so it can show the correct -// timing window when the stage is selected. The Episode 1 stage numbers should -// be specified as 51-59 (decimal) in header.flag, and the Episode 2 stage -// numbers should be specified as 61-65 (decimal). If the header.flag value is -// outside this range, it is ignored, and the Challenge Mode times window does -// not update. +// Same format as 1A/D5 command (plain text). The header.flag field is used to inform the client of the Challenge stage +// number, so it can show the correct timing window when the stage is selected. The Episode 1 stage numbers should be +// specified as 51-59 (decimal) in header.flag, and the Episode 2 stage numbers should be specified as 61-65 (decimal). +// If the header.flag value is outside this range, it is ignored, and the Challenge Mode times window does not update. // A4 (S->C): Download quest menu // Internal name: RcvQuestList -// Same format as A2, but can be used when not in a game. The client responds -// similarly as for command A2 with the following differences: +// Same format as A2, but can be used when not in a game. The client responds similarly as for command A2 with the +// following differences: // - Descriptions should be sent with the A5 command instead of A3. -// - 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. The download quest -// format is documented in create_download_quest_file in Quest.cc. -// - After the download is done, or if the player cancels the menu, the client -// sends an A0 command instead of A9. +// - 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. The download quest format is documented in create_download_quest_file in Quest.cc. +// - After the download is done, or if the player cancels the menu, the client sends an A0 command instead of A9. // A5 (S->C): Download quest information // Same format as 1A/D5 command (plain text) // A6: Open file for download // Internal name: RcvVMDownLoadHead -// Same format as 44. See the description of 44 for some notes on the -// differences between the two commands. -// Like the 44 command, the client->server form of this command is only used on -// V3 and BB. +// Same format as 44. See the description of 44 for some notes on the differences between the two commands. +// Like the 44 command, the client->server form of this command is only used on V3 and BB. // A7: Write download file // Internal name: RcvVMDownLoad -// Same format as 13. -// Like the 13 command, the client->server form of this command is only used on -// V3 and BB. +// Same format as 13. Like the 13 command, the client->server form of this command is only used on V3 and BB. // A8: Invalid command // A9 (C->S): Quest menu closed (canceled) // Internal name: SndQuestEnd // No arguments -// This command is sent when the in-game quest menu (A2) is closed. This is used -// by the server to unlock the game if the players don't select a quest, since -// players are forbidden from joining while the quest menu is open. When the -// download quest menu is closed, either by downloading a quest or canceling, -// the client sends A0 instead. -// Curiously, PSO GC sends uninitialized data in header.flag. +// This command is sent when the in-game quest menu (A2) is closed. This is used by the server to unlock the game if +// the players don't select a quest, since players are forbidden from joining while the quest menu is open. When the +// download quest menu is closed, either by downloading a quest or canceling, the client sends A0 instead. +// PSO GC sends uninitialized data in header.flag. // AA (C->S): Send quest statistic (V3/BB) // This command is generated when an opcode F92E is executed in a quest. // The server should respond with an AB command. -// This command is likely never sent by PSO GC Episodes 1&2 Trial Edition, -// because the following command (AB) is definitely not valid on that version. struct C_SendQuestStatistic_V3_BB_AA { le_uint16_t stat_id = 0; @@ -2244,27 +2024,24 @@ struct C_SendQuestStatistic_V3_BB_AA { parray params; } __packed_ws__(C_SendQuestStatistic_V3_BB_AA, 0x28); -// AB (S->C): Call quest function (V3/BB) +// AB (S->C): Start quest thread (V3/BB) // This command is not valid on PSO GC Episodes 1&2 Trial Edition. -// Upon receipt, the client starts a quest thread running the given function. -// Probably this is supposed to be one of the function IDs previously sent in -// the AA command, but the client does not check for this. The server can -// presumably use this command to call any function at any time during a quest. +// Upon receipt, the client starts a quest thread at the given label. Probably this is supposed to be one of the quest +// labels previously sent in the AA command, but the client does not check for this. The server can presumably use this +// command to execute any quest label at any time during a quest. -struct S_CallQuestFunction_V3_BB_AB { +struct S_CallQuestLabel_V3_BB_AB { le_uint16_t label = 0; parray unused; -} __packed_ws__(S_CallQuestFunction_V3_BB_AB, 4); +} __packed_ws__(S_CallQuestLabel_V3_BB_AB, 4); // AC: Quest barrier (V3/BB) // No arguments; header.flag must be 0 (or else the client disconnects) -// After a quest begins loading in a game (the server sends 44/13 commands to -// each player with the quest's data), each player will send an AC to the server -// when it has parsed the quest and is ready to start. When all players in a -// game have sent an AC to the server, the server should send them all an AC, -// 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. +// After a quest begins loading in a game (the server sends 44/13 commands to each player with the quest's data), each +// player will send an AC to the server when it has parsed the quest and is ready to start. When all players in a game +// have sent an AC to the server, the server should send them all an AC, 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. // This command is not valid on PSO GC Episodes 1&2 Trial Edition. // AD: Invalid command @@ -2273,35 +2050,30 @@ struct S_CallQuestFunction_V3_BB_AB { // B0 (S->C): Text message // Internal name: RcvEmergencyCall -// Same format as 01 command. This command is supported on DCv1 and all later -// versions, but not on prototype versions or DC NTE. -// The message appears as an overlay on the right side of the screen. The player -// doesn't do anything to dismiss it; it will disappear after a few seconds. +// Same format as 01 command. This command is supported on DCv1 and all later versions, but not on prototype versions +// or DC NTE. +// The message appears as an overlay on the right side of the screen. The player doesn't do anything to dismiss it; it +// disappears on its own after a few seconds. // B1 (C->S): Request server time // Internal name: GetServerTime -// No arguments -// Server will respond with a B1 command. +// No arguments. Server will respond with a B1 command. // B1 (S->C): Server time // Internal name: RcvServerTime -// This command is supported on DCv1 and all later versions, but not on -// prototype versions or DC NTE. -// 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 -// It seems the client ignores the date part and the milliseconds part; only the -// hour, minute, and second fields are actually used. -// This command can be sent even if it's not requested by the client (with B1). -// For example, some servers send this command every time a client joins a game. -// The time_flags fields are used on V3 and later. Time flags are a 24-bit -// little-endian integer, only 2 bits of which are used: -// time_flags_low & 1 specifies whether the client should send a ping (1D) -// every 900 frames (30 seconds). -// time_flags_low & 2 disables system interrupts during a part of the GBA game -// loading procedure. (Predictably, this is only used on GC versions.) It's -// not clear what the downstream effects of this are, or why the server -// should have any control over this behavior in the first place. -// Client will respond with a 99 command. +// This command is supported on DCv1 and all later versions, but not on prototype versions or DC NTE. +// 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 +// It seems the client ignores the date part and the milliseconds part; only the hour, minute, and second fields are +// actually used. +// This command can be sent even if it's not requested by the client (with B1). For example, some servers send this +// command every time a client joins a game. +// The time_flags fields are used on V3 and later. Time flags are a 24-bit little-endian integer, only 2 bits of which +// are used: +// time_flags_low & 1 specifies whether the client should send a ping (1D) every 900 frames (30 seconds). +// time_flags_low & 2 disables system interrupts during a part of the GBA game loading procedure. (Predictably, this +// is only used on GC versions.) It's not clear what the downstream effects of this are, or why the server should +// have any control over this behavior in the first place. +// The client will respond with a 99 command. struct S_ServerTime_B1 { /* 00 */ pstring time_str; @@ -2313,38 +2085,28 @@ struct S_ServerTime_B1 { // B2 (S->C): Execute code and/or checksum memory (DCv2 and all later versions) // Internal name: RcvProgramPatch -// Client will respond with a B3 command with the same header.flag value as was -// sent in the B2. -// On PSO PC, the code section (if included in the B2 command) is parsed and -// relocated, but is not actually executed, so the return_value field in the -// resulting B3 command is always 0. The checksum functionality does work on PSO -// PC, just like the other versions. -// This command doesn't work on some PSO GC versions, namely the later JP PSO -// Plus (v1.5), US PSO Plus (v1.2), or US/EU Episode 3. Sega presumably removed -// it after taking heat from Nintendo about enabling homebrew on the GameCube. -// On the earlier JP PSO Plus (v1.4) and JP Episode 3, this command is -// implemented as described here, with some additional compression and -// encryption steps added, similarly to how download quests are encoded. See -// send_function_call in SendCommands.cc for more details on how this works. - -// newserv supports exploiting a bug in the USA version of Episode 3, which -// re-enables the use of this command on that version of the game. See -// system/client-functions/Episode3USAQuestBufferOverflow.ppc.s for further -// details. +// Client will respond with a B3 command with the same header.flag value as was sent in the B2. +// On PSO PC, the code section (if included in the B2 command) is parsed and relocated, but is not actually executed, +// so the return_value field in the resulting B3 command is always 0. The checksum functionality does work on PSO PC, +// just like the other versions. +// This command doesn't work on some PSO GC versions, namely the later JP PSO Plus (v1.5), US PSO Plus (v1.2), or US/EU +// Episode 3. Sega presumably removed it after taking heat from Nintendo about enabling homebrew on the GameCube. +// newserv has a method to re-enable this command on those versions, which involves loading a quest at initial +// connection time. See system/quests/hidden/q88530-gc-e.bin.txt for details. On the earlier JP PSO Plus (v1.4) and JP +// Episode 3, this command is implemented as described here, with some additional compression and encryption steps +// added, similarly to how download quests are encoded. See send_function_call in SendCommands.cc for more details on +// how this works. struct S_ExecuteCode_B2 { - // If code_size == 0, no code is executed, but checksumming may still occur. - // In that case, this structure is the entire body of the command (no code - // or footer is sent). + // If code_size == 0, no code is executed, but checksumming may still occur. In that case, this structure is the + // entire body of the command (no code or footer is sent). le_uint32_t code_size = 0; // Size of code (following this struct) and footer le_uint32_t checksum_start = 0; // May be null if size is zero le_uint32_t checksum_size = 0; // If zero, no checksum is computed - // The code immediately follows, ending with a RELFileFooter. The REL's root - // offset is relative to the start of the code object here, so an offset of 0 - // refers to the byte after checksum_size. The root offset points to the - // offset ot the entrypoint (that is, the entrypoint offset is doubly - // indirect). This is presumably done so the entrypoint can be optionally - // relocated. + // The code immediately follows, ending with a RELFileFooter. The REL's root offset is relative to the start of the + // code object here, so an offset of 0 refers to the byte after checksum_size. The root offset points to the offset + // to the entrypoint (that is, the entrypoint offset is doubly indirect). This is presumably done so the entrypoint + // can be optionally relocated. } __packed_ws__(S_ExecuteCode_B2, 0x0C); // B3 (C->S): Execute code and/or checksum memory result @@ -2352,9 +2114,9 @@ struct S_ExecuteCode_B2 { struct C_ExecuteCodeResult_B3 { // On DC, return_value has the value in r0 when the function returns. - // On PC, return_value is always 0. + // On PC, return_value is always 0 since the code section is not executed. // On GC, return_value has the value in r3 when the function returns. - // On XB and BB, return_value has the value in eax when the function returns. + // On Xbox and BB, return_value has the value in eax when the function returns. // If code_size was 0 in the B2 command, return_value is always 0. le_uint32_t return_value = 0; le_uint32_t checksum = 0; // 0 if no checksum was computed @@ -2367,12 +2129,11 @@ struct C_ExecuteCodeResult_B3 { // B7 (S->C): Rank update (Episode 3) struct S_RankUpdate_Ep3_B7 { - // On non-NTE versions, if rank is not zero, the client sets its rank text to - // ":", truncated to 11 characters. If rank is zero or the - // client is NTE, the client uses rank_text without modifying it. + // On non-NTE versions, if rank is not zero, the client sets its rank text to ":", truncated to 11 + // characters. If rank is zero or the client is Ep3 NTE, the client uses rank_text without modifying it. le_uint32_t rank = 0; pstring rank_text; - // The remaining fields are ignored by NTE: + // The remaining fields are ignored by Ep3 NTE: le_uint32_t current_meseta = 0; le_uint32_t total_meseta_earned = 0; le_uint32_t unlocked_jukebox_songs = 0xFFFFFFFF; @@ -2383,12 +2144,10 @@ struct S_RankUpdate_Ep3_B7 { // The client sends this after it receives a B7 from the server. // B8 (S->C): Update card definitions (Episode 3) -// Contents is a single le_uint32_t specifying the size of the (PRS-compressed) -// data, followed immediately by the data. The maximum size of the compressed -// data is 0x9000 bytes, although the receive buffer size limit applies first in -// practice, which limits this to 0x7BF8 bytes. The maximum size of the -// decompressed data is 0x36EC0 bytes on retail Episode 3, or 0x32960 bytes on -// Trial Edition. +// Contents is a single le_uint32_t specifying the size of the (PRS-compressed) data, followed immediately by the data. +// The maximum size of the compressed data is 0x9000 bytes, although the receive buffer size limit applies first in +// practice, which limits this to 0x7BF8 bytes. The maximum size of the decompressed data is 0x36EC0 bytes on retail +// Episode 3, or 0x32960 bytes on Trial Edition. // Note: PSO BB accepts this command as well, but ignores it. // B8 (C->S): Confirm updated card definitions (Episode 3) @@ -2396,8 +2155,7 @@ struct S_RankUpdate_Ep3_B7 { // The client sends this after it receives a B8 from the server. // B8 (C->S): Valid but ignored (BB) -// The client accepts this command, but ignores it. It may have had some -// later-removed purpose during BB's development. +// The client accepts this command, but ignores it. It may have had some later-removed purpose during BB's development. // B9 (S->C): Update CARD lobby media (Episode 3) // This command is not valid on Episode 3 Trial Edition. @@ -2409,79 +2167,69 @@ struct S_UpdateMediaHeader_Ep3_B9 { // 3: Animation // 4: Delete all previous media updates // Any other value: entire command is ignored - // A texture can be displayed without a model or animation. A model requires a - // texture (sent in a separate B9 command with the same location_flags), but - // does not require an animation - it will just stand still without one. An - // animation requires both a texture and model with the same location_flags. - // For models and animations, the game looks for various tokens in the - // decompressed data; specifically '****', 'GCAM', 'GJBM', 'GJTL', 'GLIM', - // 'GMDM', 'GSSM', 'NCAM', 'NJBM', 'NJCA', 'NLIM', 'NMDM', and 'NSSM'. + // A texture can be displayed without a model or animation. A model requires a texture (sent in a separate B9 command + // with the same location_flags), but does not require an animation - it will just stand still without one. An + // animation requires both a texture and model with the same location_flags. For models and animations, the game + // looks for various tokens in the decompressed data; specifically '****', 'GCAM', 'GJBM', 'GJTL', 'GLIM', 'GMDM', + // 'GSSM', 'NCAM', 'NJBM', 'NJCA', 'NLIM', 'NMDM', and 'NSSM'. le_uint32_t type = 0; - // location_flags is a bit field specifying where the banner or object should - // appear. The bits are: - // 00000001: South above-counter banner (facing away from teleporters) - // 00000002: West above-counter banner - // 00000004: North above-counter banner (facing toward jukebox) - // 00000008: East above-counter banner - // 00000010: Banner above west (left) teleporter - // 00000020: Banner above east (right) teleporter - // 00000040: Banner at south end of lobby (opposite the jukebox) - // 00000080: Immediately left of 00000040 - // 00000100: Immediately right of 00000040 - // 00000200: Same as 00000080, but further left and at a slight inward angle - // 00000400: Same as 00000100, but further right and at a slight inward angle - // 00000800: Banner at north end of lobby, above the jukebox - // 00001000: Immediately right of 00000800 - // 00002000: Immediately left of 00000800 - // 00004000: Same as 00001000, but further right and at a slight inward angle - // 00008000: Same as 00002000, but further left and at a slight inward angle - // 00010000: Banners at west AND east ends of lobby, next to battle tables - // 00020000: Immediately left of 00010000 (2 banners) - // 00040000: Immediately right of 00010000 (2 banners) - // 00080000: Banners on southwest AND southeast ends of the lobby - // 00100000: Banners on south-southwest AND south-southeast ends of the lobby - // 00200000: Floor banners in front of the counter (4 banners) - // 00400000: Banners on both small walls in front of the battle tables - // 00800000: On southern platform - // 01000000: In front of jukebox - // 02000000: In western battle table corner (next to 4-player tables) - // 04000000: In eastern battle table corner (next to 2-player tables) - // 08000000: In southeastern battle table corner (next to 2-player tables) - // 10000000: In southwestern battle table corner (next to 4-player tables) - // 20000000: Just north-northwest of the counter - // 40000000: In front of the small wall in front of the 2-player battle tables - // 80000000: Inside the lobby counter, facing southeast - // Positions 00800000 and above appear to be intended for models and not - // banners - if a banner is sent in these locations, it appears sideways and - // halfway submerged in the floor, and has no collision. Furthermore, it seems - // that up to 8 different banners or models may be set simultaneously (though - // each may appear in more than one position). If 8 banners or objects already - // exist, further media sent via B9 is ignored. + // location_flags is a bit field specifying where the banner or object should appear. The bits are: + // 00000001: South above-counter banner (facing away from teleporters) + // 00000002: West above-counter banner + // 00000004: North above-counter banner (facing toward jukebox) + // 00000008: East above-counter banner + // 00000010: Banner above west (left) teleporter + // 00000020: Banner above east (right) teleporter + // 00000040: Banner at south end of lobby (opposite the jukebox) + // 00000080: Immediately left of 00000040 + // 00000100: Immediately right of 00000040 + // 00000200: Same as 00000080, but further left and at a slight inward angle + // 00000400: Same as 00000100, but further right and at a slight inward angle + // 00000800: Banner at north end of lobby, above the jukebox + // 00001000: Immediately right of 00000800 + // 00002000: Immediately left of 00000800 + // 00004000: Same as 00001000, but further right and at a slight inward angle + // 00008000: Same as 00002000, but further left and at a slight inward angle + // 00010000: Banners at west AND east ends of lobby, next to battle tables + // 00020000: Immediately left of 00010000 (2 banners) + // 00040000: Immediately right of 00010000 (2 banners) + // 00080000: Banners on southwest AND southeast ends of the lobby + // 00100000: Banners on south-southwest AND south-southeast ends of the lobby + // 00200000: Floor banners in front of the counter (4 banners) + // 00400000: Banners on both small walls in front of the battle tables + // 00800000: On southern platform + // 01000000: In front of jukebox + // 02000000: In western battle table corner (next to 4-player tables) + // 04000000: In eastern battle table corner (next to 2-player tables) + // 08000000: In southeastern battle table corner (next to 2-player tables) + // 10000000: In southwestern battle table corner (next to 4-player tables) + // 20000000: Just north-northwest of the counter + // 40000000: In front of the small wall in front of the 2-player battle tables + // 80000000: Inside the lobby counter, facing southeast + // Positions 00800000 and above appear to be intended for models and not banners - if a banner is sent in these + // locations, it appears sideways and halfway submerged in the floor, and has no collision. Furthermore, it seems + // that up to 8 different banners or models may be set simultaneously (though each may appear in more than one + // position). If 8 banners or objects already exist, further media sent via B9 is ignored. le_uint32_t location_flags = 0x00000000; - // This field specifies the size of the compressed data. The uncompressed size - // is not sent anywhere in this command. + // This field specifies the size of the compressed data. The uncompressed size is not sent anywhere in this command. le_uint16_t size = 0; le_uint16_t unused = 0; - // The PRS-compressed data immediately follows this header. The maximum size - // of the compressed data is 0x3800 bytes, and it must decompress to fewer - // than 0x37000 bytes of output. If either of these limits are violated, the - // client ignores the command. + // If type is 1, 2, or 3, the PRS-compressed data immediately follows this header. The maximum size of the compressed + // data is 0x3800 bytes, and it must decompress to fewer than 0x37000 bytes of output. If either of these limits are + // violated, the client ignores the command. } __packed_ws__(S_UpdateMediaHeader_Ep3_B9, 0x0C); // B9 (C->S): Confirm media update (Episode 3) -// No arguments +// No arguments. The client sends this even if it ignores the contents of a B9 command. // This command is not valid on Episode 3 Trial Edition. -// The client sends this even if it ignores the contents of a B9 command. // BA: Meseta transaction (Episode 3) // This command is not valid on Episode 3 Trial Edition. // header.flag specifies the transaction purpose. Specific known values: -// 01 = Initialize Meseta subsystem (C->S; always has a value of 0) -// 02 = Spend Meseta (at e.g. lobby jukebox or Pinz's shop) (C->S) -// 03 = Successful transaction (S->C; request_token must match the last token -// sent by client) -// 04 = Insufficient Meseta (S->C; request_token must match the last token sent -// by client) +// 01 = Initialize Meseta subsystem (C->S; always has a value of 0) +// 02 = Spend Meseta (at e.g. lobby jukebox or Pinz's shop) (C->S) +// 03 = Successful transaction (S->C; request_token must match the last token sent by client) +// 04 = Insufficient Meseta (S->C; request_token must match the last token sent by client) struct C_MesetaTransaction_Ep3_BA { le_uint32_t transaction_num = 0; @@ -2497,10 +2245,9 @@ struct S_MesetaTransaction_Ep3_BA { // BB (S->C): Tournament spectator team list (Episode 3) // This command is not valid on Episode 3 Trial Edition. -// This is sent in response to an 09 command when viewing the tournament list -// for spectating. newserv doesn't implement this because tournaments work -// differently here - there is no fixed start time, and tournament spectator -// teams behave the same way as free-battle spectator teams. +// This is sent in response to an 09 command when viewing the tournament list for spectating. newserv doesn't implement +// this because tournaments work differently here - there is no fixed start time, and tournament spectator teams behave +// the same way as free-battle spectator teams. // header.flag is the number of game entries. struct Ep3TournamentBracketEntry { // Also used in commands CC and E3 @@ -2533,8 +2280,7 @@ struct S_TournamentSpectatorTeamList_Ep3_BB { // C0 (C->S): Request choice search options (DCv2 and later versions) // Internal name: GetChoiceList -// No arguments -// Server should respond with a C0 command (described below). +// No arguments. Server should respond with a C0 command (described below). // C0 (S->C): Choice search options (DCv2 and later versions) // Internal name: RcvChoiceList @@ -2542,8 +2288,8 @@ struct S_TournamentSpectatorTeamList_Ep3_BB { // Command is a list of these; header.flag is the entry count (incl. top-level). template struct S_ChoiceSearchEntryT { - // 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. + // 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_choice_id = 0; // 0 for top-level categories le_uint16_t choice_id = 0; pstring text; @@ -2553,9 +2299,8 @@ using S_ChoiceSearchEntry_PC_BB_C0 = S_ChoiceSearchEntryT; check_struct_size(S_ChoiceSearchEntry_DC_V3_C0, 0x20); check_struct_size(S_ChoiceSearchEntry_PC_BB_C0, 0x3C); -// 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): +// 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" @@ -2571,8 +2316,7 @@ check_struct_size(S_ChoiceSearchEntry_PC_BB_C0, 0x3C); template struct C_CreateGameBaseT { - // menu_id and item_id are only used for the E7 (create spectator team) form - // of this command + // menu_id and item_id are only used for the E7 (create spectator team) form of this command le_uint32_t menu_id = 0; le_uint32_t item_id = 0; pstring name; @@ -2585,12 +2329,10 @@ template struct C_CreateGameT : C_CreateGameBaseT { Difficulty difficulty = Difficulty::NORMAL; // Always NORMAL on Episode 3 uint8_t battle_mode = 0; // 0 or 1 (always 0 on Episode 3) - // Note: Episode 3 uses the challenge mode flag for view battle permissions. - // 0 = view battle allowed; 1 = not allowed + // Note: Episode 3 uses the challenge mode flag for view battle permissions: 0 = view battle allowed; 1 = not allowed uint8_t challenge_mode = 0; // 0 or 1 - // Note: According to the Sylverant wiki, in v2-land, the episode field has a - // different meaning: if set to 0, the game can be joined by v1 and v2 - // players; if set to 1, it's v2-only. + // Note: According to the Sylverant wiki, in v2-land, the episode field has a different meaning: if set to 0, the + // game can be joined by v1 and v2 players; if set to 1, it's v2-only. uint8_t episode = 0; // 1-4 on V3+ (3 on Episode 3); unused on DC/PC } __attribute__((packed)); using C_CreateGame_DC_V3_0C_C1_Ep3_EC = C_CreateGameT; @@ -2605,19 +2347,16 @@ struct C_CreateGame_BB_C1 : C_CreateGameT { // C2 (C->S): Set choice search parameters (DCv2 and later versions) // Internal name: PutChoiceList -// Server does not respond. -// Contents is a ChoiceSearchConfig, which is defined in PlayerSubordinates.hh. +// Server does not respond. Contents is a ChoiceSearchConfig, which is defined in PlayerSubordinates.hh. // C3 (C->S): Execute choice search (DCv2 and later versions) // Internal name: SndChoiceSeq -// Same format as C2. The disabled field is unused. -// Server should respond with a C4 command. +// Same format as C2. The disabled field is unused. The server responds with a C4 command. // C4 (S->C): Choice search results (DCv2 and later versions) // Internal name: RcvChoiceAns -// There is a bug that can cause the client to crash or display garbage if this -// command is sent with no entries. To work around this, newserv sends a blank -// entry (but still with header.flag = 0) if there are no results. +// There is a bug that can cause the client to crash or display garbage if this command is sent with no entries. To +// work around this, newserv sends a blank entry (but still with header.flag = 0) if there are no results. // Command is a list of these; header.flag is the entry count template @@ -2642,14 +2381,11 @@ check_struct_size(S_ChoiceSearchResultEntry_BB_C4, 0x158); // C5 (S->C): Player records update (DCv2 and later versions) // Internal name: RcvChallengeData -// Command is a list of PlayerRecordsEntry structures; header.flag specifies -// the entry count. -// The server sends this command when a player joins a lobby to update the -// challenge mode records of all the present players. +// Command is a list of PlayerRecordsEntry structures; header.flag specifies the entry count. +// The server sends this command when a player joins a lobby to update the challenge mode records of all the players. // C6 (C->S): Set blocked senders list (V3/BB) -// The command always contains the same number of entries, even if the entries -// at the end are blank (zero). +// The command always contains the same number of entries, even if the entries at the end are blank (zero). template struct C_SetBlockedSendersT_C6 { @@ -2661,52 +2397,41 @@ check_struct_size(C_SetBlockedSenders_V3_C6, 0x78); check_struct_size(C_SetBlockedSenders_BB_C6, 0x70); // C7 (C->S): Enable simple mail auto-reply (V3/BB) -// Same format as 1A/D5 command (plain text). -// Server does not respond +// Same format as 1A/D5 command (plain text); server does not respond. // C8 (C->S): Disable simple mail auto-reply (V3/BB) -// No arguments -// Server does not respond +// No arguments; server does not respond. // C9: Broadcast command (Episode 3) // Internal name: SndCardClientData // Same as 60, but should be forwarded only to Episode 3 clients. -// newserv uses this command for all server-generated events (in response to CA -// commands), except for map data requests. This differs from Sega's original -// implementation, which sent CA responses via 60 commands instead. +// newserv uses this command for all server-generated events (in response to CA), except for map data requests. This +// differs from Sega's original implementation, which sent responses to CA via 60 instead. // C9 (C->S): Change connection status (Xbox) -// header.flag specifies if the player's online status should be hidden; 1 means -// shown, 2 means hidden. +// header.flag specifies if the player's online status should be hidden; 1 means shown, 2 means hidden. // CA (C->S): Server data request (Episode 3) // Internal name: SndCardServerData -// The CA command format is the same as that of the 6xB3 commands, and the -// subsubcommands formats are shared as well. Unlike the 6x commands, the server -// is expected to respond to the command appropriately instead of forwarding it. -// Because the formats are shared, the 6xB3 commands are also referred to as CAx -// commands in the comments and structure names. +// The CA command format is the same as that of the 6xB3 commands, and the subsubcommands formats are shared as well. +// Unlike the 6x commands, the server is expected to respond to the command appropriately instead of forwarding it. +// Because the formats are shared, the 6xB3 commands are also referred to as CAx commands. // CB: Broadcast command (Episode 3) // Internal name: SndKansenPsoData // Same as 60, but only send to Episode 3 clients. -// This command's format is identical to C9, except that CB is not valid on -// Episode 3 Trial Edition (whereas C9 is valid). -// Unlike the 6x and C9 commands, subcommands sent with the CB command are -// forwarded from spectator teams to the primary team. The client only uses this -// behavior for the 6xBE command (sound chat), and newserv enforces that no -// other subcommand can be sent via CB. +// This command's format is identical to C9, except that CB is not valid on Ep3 NTE (whereas C9 is valid). +// Unlike the 6x and C9 commands, subcommands sent with the CB command are forwarded from spectator teams ("watcher +// lobbies" in newserv) to the primary team ("watched lobby"). The client only uses this behavior for the 6xBE command +// (sound chat), and newserv enforces that no other subcommand can be sent via CB. // CC (S->C): Confirm tournament entry (Episode 3) // This command is not valid on Episode 3 Trial Edition. -// header.flag determines the client's registration state - 1 if the client is -// registered for the tournament, 0 if not. -// This command controls what's shown in the Check Tactics pane in the pause -// menu. If the client is registered (header.flag==1), the option is enabled and -// the bracket data in the command is shown there, and a third pane on the -// Status item shows the other information (tournament name, ship name, and -// start time). If the client is not registered, the Check Tactics option is -// disabled and the Status item has only two panes. +// header.flag determines the client's registration state - 1 if the client is registered for the tournament, 0 if not. +// This command controls what's shown in the Check Tactics pane in the pause menu. If the client is registered +// (header.flag==1), the option is enabled and the bracket data in the command is shown there, and a third pane on the +// Status item shows the other information (tournament name, ship name, and start time). If the client is not +// registered, the Check Tactics option is disabled and the Status item has only two panes. struct S_ConfirmTournamentEntry_Ep3_CC { pstring tournament_name; @@ -2724,87 +2449,66 @@ struct S_ConfirmTournamentEntry_Ep3_CC { // CF: Invalid command // D0 (C->S): Start trade sequence (V3/BB) -// The trade window sequence is a bit complicated. On pre-BB versions, the -// normal flow is: +// The trade window sequence is a bit complicated. On pre-BB versions, the normal flow is: // - Clients sync trade state with 6xA6 commands // - When both have confirmed, one client (the initiator) sends D0 // - The server sends D1 to the other client (the responder) // - The responder sends D0 // - The server sends D1 to both clients -// - Both clients delete the sent items from their inventories and send the -// appropriate subcommand (6x29) -// - Both clients send D2; similarly to how AC works, the server doesn't -// proceed until both D2 commands are received -// - The server sends D3 to both clients with each other's data from their D0 -// commands, followed immediately by D4 01 to both clients, which completes -// the trade +// - Both clients delete the sent items from their inventories and send the appropriate subcommand (6x29) +// - Both clients send D2; similarly to how AC works, the server doesn't proceed until both D2 commands are received +// - The server sends D3 to both clients with each other's data from their D0 commands, followed immediately by D4 01 +// to both clients, which completes the trade // - Both clients send the appropriate subcommand to create inventory items -// On BB, the flow is similar, except after both D2 commands are received, the -// server instead handles the rest of the process - it sends 6x29 commands to -// delete the inventory items and 6xBE to create the traded items. -// At any point if an error occurs, either client may send a D4 00, which -// cancels the entire sequence. The server should then send D4 00 to both -// clients. +// On BB, the flow is similar, except after both D2 commands are received, the server instead handles the rest of the +// process - it sends 6x29 commands to delete the inventory items and 6xBE to create the traded items. +// At any point if an error occurs, either client may send a D4 00, which cancels the entire sequence. The server +// should then send D4 00 to both clients. struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server le_uint16_t target_client_id = 0; le_uint16_t item_count = 0; - // Note: PSO GC sends uninitialized data in the unused entries of this - // command. newserv parses and regenerates the item data when sending D3, - // which effectively erases the uninitialized data. + // Note: PSO GC sends uninitialized data in the unused entries of this command. newserv parses and regenerates the + // item data when sending D3, which effectively erases the uninitialized data. parray item_datas; } __packed_ws__(SC_TradeItems_D0_D3, 0x284); // D1 (S->C): Advance trade state (V3/BB) -// No arguments -// See D0 description for usage information. +// No arguments. See the description of D0 for usage information. // D2 (C->S): Trade can proceed (V3/BB) -// No arguments -// See D0 description for usage information. +// No arguments. See the description of D0 for usage information. // D3 (S->C): Execute trade (V3/BB) -// On V3, this command has the same format as D0. See the D0 description for -// usage information. -// On BB, this command has no arguments (and the server generates the -// appropriate delete and create inventory item commands), but the D3 command -// must still must be sent before the D4 command to advance the trade state. +// On V3, this command has the same format as D0. See the description of D0 for usage information. +// On BB, this command has no arguments (and the server generates the appropriate delete and create inventory item +// commands), but the D3 command must still must be sent before the D4 command to advance the trade state. // D4 (C->S): Trade failed (V3/BB) -// No arguments -// See D0 description for usage information. +// No arguments. See the description of D0 for usage information. // D4 (S->C): Trade complete (V3/BB) -// header.flag must be 0 (trade failed) or 1 (trade complete). -// See D0 description for usage information. +// header.flag must be 0 (trade failed) or 1 (trade complete). See the description of D0 for usage information. // D5: Large message box (V3/BB) -// Same as 1A command, except the maximum length of the message is 0x1000 -// bytes. On BB, this command is not valid during the data server phase -// (whereas 1A is valid there). The BB client ignores all D5 commands after the -// first one sent in each connection; this logic does not apply to 1A. +// Same as 1A command, except the maximum length of the message is 0x1000 bytes. On BB, this command is not valid +// during the data server phase (whereas 1A is valid there). The BB client ignores all D5 commands after the first one +// sent in each connection; this logic does not apply to 1A. // D6 (C->S): Large message box closed (V3) // No arguments -// DC, PC, and BB do not send this command at all. GC US v1.0 and v1.1 will send -// this command when any large message box (1A/D5) is closed; GC Plus and -// Episode 3 will send D6 only for large message boxes that occur before the -// client has joined 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.) In some of these versions, there is a bug that sets an -// incorrect interaction mode when the message box is closed while the player is -// in the lobby; some servers (e.g. Schtserv) send a lobby welcome message -// anyway, along with an 01 (lobby message box) which properly sets the -// interaction mode when closed. +// DC, PC, and BB do not send this command at all. GC US v1.0 and v1.1 will send this command when any large message +// box (1A/D5) is closed; GC Plus and Episode 3 will send D6 only for large message boxes that occur before the client +// has joined 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.) In some of these versions, there is a bug that sets an incorrect +// interaction mode when the message box is closed while the player is in the lobby; some servers (e.g. Schtserv) send +// a lobby welcome message anyway, along with an 01 (lobby message box) which fixes the interaction mode when closed. // D7 (C->S): Request GBA game file (V3) -// This command is sent when the client executes the file_dl_req (F8C0) quest -// opcode. header.flag contains the value of the opcode's first argument; the -// second argument is a pointer to the filename. -// The server should send the requested file using A6/A7 commands; if the file -// does not exist, the server should reply with a D7 command. -// This command exists on XB as well, but it presumably is never sent by the -// client. +// This command is sent when the client executes the file_dl_req (F8C0) quest opcode. header.flag contains the value of +// the opcode's first argument; the second argument is a pointer to the filename. The server should send the requested +// file using A6/A7 commands; if the file does not exist, the server should reply with a D7 command. +// This command exists on XB as well, but it presumably is never sent by the client. struct C_GBAGameRequest_V3_D7 { pstring filename; @@ -2812,21 +2516,18 @@ struct C_GBAGameRequest_V3_D7 { // D7 (S->C): GBA file not found (V3/BB) // No arguments -// This command is not valid on PSO GC Episodes 1&2 Trial Edition. -// This command tells the client that the file it requested via a D7 command -// does not exist. This causes the F8C1 (get_dl_status) quest opcode to return -// 0 (file not found), rather than 1 (download in progress) or 2 (complete). -// PSO BB accepts but completely ignores this command. +// This command is not valid on PSO GC Episodes 1&2 Trial Edition. PSO BB accepts but completely ignores this command. +// This command tells the client that the file it requested via a D7 command does not exist. This causes the F8C1 +// (get_dl_status) quest opcode to return 0 (file not found), rather than 1 (download in progress) or 2 (complete). // D8 (C->S): Info board request (V3/BB) -// No arguments -// The server should respond with a D8 command (described below). +// No arguments. The server should respond with a D8 command. // D8 (S->C): Info board contents (V3/BB) // This command is not valid on PSO GC Episodes 1&2 Trial Edition. -// 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. +// 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_InfoBoardEntryT_D8 { pstring name; @@ -2838,8 +2539,7 @@ check_struct_size(S_InfoBoardEntry_V3_D8, 0xBC); check_struct_size(S_InfoBoardEntry_BB_D8, 0x178); // D9 (C->S): Write info board (V3/BB) -// Contents are plain text, like 1A/D5. -// Server does not respond +// Contents are plain text, like 1A/D5. Server does not respond. // DA (S->C): Change lobby event (V3/BB) // header.flag = new event number; no other arguments. @@ -2860,8 +2560,8 @@ struct C_VerifyAccount_V3_DB { pstring password; // On XB, this contains "xbox-pso" } __packed_ws__(C_VerifyAccount_V3_DB, 0xDC); -// Note: This login pathway generally isn't used on BB (and isn't supported at -// all during the data server phase). All current servers use 03/93 instead. +// Note: This login pathway generally isn't used on BB (and isn't supported at all during the data server phase). All +// current servers use 03/93 instead. struct C_VerifyAccount_BB_DB { // Note: These four fields are likely the same as those used in BB's 9E pstring v1_serial_number; // Always blank? @@ -2875,11 +2575,10 @@ struct C_VerifyAccount_BB_DB { } __packed_ws__(C_VerifyAccount_BB_DB, 0xD4); // DC: Set battle in progress flag (Episode 3) -// No arguments except header.flag when sent by the client. When header.flag is -// 1, the game should be locked - no players should be allowed to join. In this -// case, the client waits for the server to respond with another DC command -// before proceeding with battle setup. When header.flag is 0, the game should -// be unlocked, and the client does not wait for a response from the server. +// No arguments except header.flag when sent by the client. When header.flag is 1, the game should be locked - no +// players should be allowed to join. In this case, the client waits for the server to respond with another DC command +// before proceeding with battle setup. When header.flag is 0, the game should be unlocked, and the client does not +// wait for a response from the server. // DC: Guild card data (BB) @@ -2902,11 +2601,9 @@ struct C_GuildCardDataRequest_BB_03DC { } __packed_ws__(C_GuildCardDataRequest_BB_03DC, 0x0C); // DD (S->C): Send quest state to joining player (BB) -// When a player joins a game with a quest already in progress, the server -// should send this command to the leader. header.flag is the client ID that the -// leader should send quest state to; the leader will then send a series of -// target commands (62/6D) that the server can forward to the joining player. -// No other arguments +// When a player joins a game with a quest already in progress, the server should send this command to the leader. +// No arguments except header.flag, which is the client ID that the leader should send quest state to. The leader will +// then send a series of target commands (62/6D) that the server can forward to the joining player. // DE (S->C): Rare monster list (BB) @@ -2954,9 +2651,8 @@ struct C_CreateChallengeModeAwardItem_BB_07DF { } __packed_ws__(C_CreateChallengeModeAwardItem_BB_07DF, 0x1C); // E0 (S->C): Tournament list (Episode 3) -// The client will send 09 and 10 commands to inspect or enter a tournament. The -// server should respond to an 09 command with an E3 command; the server should -// respond to a 10 command with an E2 command. +// The client will send 09 and 10 commands to inspect or enter a tournament. The server should respond to an 09 command +// with an E3 command; the server should respond to a 10 command with an E2 command. // header.flag is the count of filled-in entries. struct S_TournamentList_Ep3NTE_E0 { @@ -2986,21 +2682,20 @@ struct S_TournamentList_Ep3_E0 { uint8_t unknown_a1 = 0; uint8_t locked = 0; // If nonzero, the lock icon appears in the menu // Values for the state field: - // 00 = Preparing - // 01 = 1st Round - // 02 = 2nd Round - // 03 = 3rd Round - // 04 = Semifinals - // 05 = Entries no longer accepted - // 06 = Finals - // 07 = Preparing for Battle - // 08 = Battle in progress - // 09 = Preparing to view Battle - // 0A = Viewing a Battle - // Values beyond 0A don't appear to cause problems, but cause strings to - // appear that are obviously not intended to appear in the tournament list, - // like "View the board" and "Board: Write". (In fact, some of the strings - // listed above may be unintended for this menu as well.) + // 00 = Preparing + // 01 = 1st Round + // 02 = 2nd Round + // 03 = 3rd Round + // 04 = Semifinals + // 05 = Entries no longer accepted + // 06 = Finals + // 07 = Preparing for Battle + // 08 = Battle in progress + // 09 = Preparing to view Battle + // 0A = Viewing a Battle + // Values beyond 0A don't appear to cause problems, but cause strings to appear that are obviously not intended to + // appear in the tournament list, like "View the board" and "Board: Write". (In fact, some of the strings listed + // above may be unintended for this menu as well.) uint8_t state = 0; uint8_t unknown_a2 = 0; le_uint32_t start_time = 0; // In seconds since Unix epoch @@ -3011,8 +2706,8 @@ struct S_TournamentList_Ep3_E0 { le_uint16_t unknown_a4 = 0xFFFF; } __packed_ws__(Entry, 0x38); parray entries; - // These fields exist in the command (the copy constructor copies them over) - // but it seems they aren't used by the client at all. + // These fields exist in the command (the copy constructor copies them over) but it seems they aren't used by the + // client at all. uint8_t unknown_a1 = 0; uint8_t unknown_a2 = 0; uint8_t unknown_a3 = 0; @@ -3023,12 +2718,10 @@ struct S_TournamentList_Ep3_E0 { // No arguments. The server should respond with an E1 or E2 command. // E1 (S->C): Game information (Episode 3) -// The header.flag argument determines which fields are valid (and which panes -// should be shown in the information window). The values are the same as for -// the E3 command, but each value only makes sense for one command. That is, 00, -// 01, and 04 should be used with the E1 command, while 02, 03, and 05 should be -// used with the E3 command. See the E3 command for descriptions of what each -// flag value means. +// The header.flag argument determines which fields are valid (and which panes should be shown in the information +// window). The values are the same as for the E3 command, but each value only makes sense for one command. That is, +// 00, 01, and 04 should be used with the E1 command, while 02, 03, and 05 should be used with the E3 command. See the +// E3 command for descriptions of what each flag value means. template struct S_GameInformationBaseT_Ep3_E1 { @@ -3049,41 +2742,30 @@ check_struct_size(S_GameInformation_Ep3NTE_E1, 0x28C); check_struct_size(S_GameInformation_Ep3_E1, 0x294); // E1 (S->C): System file created (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 system file, so the -// client should use appropriate defaults. +// 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 system file, so the client should use appropriate defaults. struct S_SystemFileCreated_00E1_BB { - // If success is not equal to 1, the client shows a message saying "Forced - // server disconnect (907)" and disconnects. Otherwise, the client proceeeds - // as if it had received an 00E2 command, and sends its first 00E3. + // If success is not equal to 1, the client shows a message saying "Forced server disconnect (907)" and disconnects. + // Otherwise, the client proceeeds as if it had received an 00E2 command, and sends its first 00E3. le_uint32_t success = 1; } __packed_ws__(S_SystemFileCreated_00E1_BB, 4); // E2 (C->S): Tournament control (Episode 3) -// No arguments (in any of its forms) except header.flag, which determines ths -// command's meaning. Specifically: -// flag=00: request tournament list (server responds with E0) -// flag=01: check tournament (server responds with E2) -// flag=02: cancel tournament entry (server responds with CC) -// flag=03: create tournament spectator team (server responds with E0) -// flag=04: join tournament spectator team (server responds with E0) -// In case 02, the resulting CC command has header.flag = 0 to indicate the -// player is no longer registered. -// In cases 03 and 04, the client handles follow-ups differently from the 00 -// case. In case 04, the client will send a 10 (to which the server responds -// with an E2), but when a choice is made from that menu, the client sends E6 01 -// instead of 10. In case 03, the flow is similar, but the client sends E7 -// instead of E6 01. -// newserv responds with a standard ship info box (11), but this seems not to be -// intended since it partially overlaps some windows. +// No arguments (in any of its forms) except header.flag, which determines the command's meaning. Specifically: +// 00: request tournament list (server responds with E0) +// 01: check tournament (server responds with E2) +// 02: cancel tournament entry (server responds with CC with header.flag = 0) +// 03: create tournament spectator team (server responds with E0) +// 04: join tournament spectator team (server responds with E0) +// In cases 03 and 04, the client handles follow-ups differently from the 00 case. In case 04, the client will send a +// 10 (to which the server responds with an E2), but when a choice is made from that menu, the client sends E6 01 +// instead of 10. In case 03, the flow is similar, but the client sends E7 instead of E6 01. // E2 (S->C): Tournament entry list (Episode 3) -// Client may send 09 commands if the player presses X. It's not clear what the -// server should respond with in this case. -// If the player selects an entry slot, client will respond with a 10 command -// containing both a team name and password (with flag = 3). The server should -// respond to that with a CC command. +// Client may send 09 commands if the player presses X. It's not clear what the server should respond with. +// If the player selects an entry slot, client will respond with a 10 command containing both a team name and password +// (with flag = 3). The server should respond to that with a CC command. struct S_TournamentEntryList_Ep3_E2 { le_uint16_t players_per_team = 0; @@ -3092,15 +2774,15 @@ struct S_TournamentEntryList_Ep3_E2 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; uint8_t unknown_a1 = 0; - // If locked is nonzero, a lock icon appears next to this team and the - // player is prompted for a password if they select this team. + // If locked is nonzero, a lock icon appears next to this team and the player is prompted for a password if they + // select this team. uint8_t locked = 0; // State values: - // 00 = empty (team_name is ignored; entry is selectable) - // 01 = present, joinable (team_name renders in white) - // 02 = present, finalized (team_name renders in yellow) - // If state is any other value, the entry renders as if its state were 02, - // but cannot be selected at all (the menu cursor simply skips over it). + // 00 = empty (team_name is ignored; entry is selectable) + // 01 = present, joinable (team_name renders in white) + // 02 = present, finalized (team_name renders in yellow) + // If state is any other value, the entry renders as if its state were 02, but cannot be selected at all (the menu + // cursor simply skips over it). uint8_t state = 0; uint8_t unknown_a2 = 0; pstring name; @@ -3117,26 +2799,16 @@ struct S_SyncSystemFile_BB_E2 { } __packed_ws__(S_SyncSystemFile_BB_E2, 0xAF0); // E3 (S->C): Game or tournament info (Episode 3) -// The header.flag argument determines which fields are valid (and which panes -// should be shown in the information window). The values are: -// flag=00: Opponents pane only -// flag=01: Opponents and Rules panes -// flag=02: Rules and bracket panes (the bracket pane uses the tournament's name -// as its title) -// flag=03: Opponents, Rules, and bracket panes -// flag=04: Spectators and Opponents pane -// flag=05: Spectators, Opponents, Rules, and bracket panes -// Sending other values in the header.flag field results in a blank info window -// with unintended strings appearing in the window title. -// Presumably the cases above would be used in different scenarios, probably: -// 00: When inspecting a non-tournament game with no battle in progress -// 01: When inspecting a non-tournament game with a battle in progress -// 02: When inspecting tournaments that have not yet started -// 03: When inspecting a tournament match -// 04: When inspecting a non-tournament spectator team -// 05: When inspecting a tournament spectator team -// The 00, 01, and 04 cases don't really make sense, because the E1 command is -// more appropriate for inspecting non-tournament games. +// The header.flag argument determines which fields are valid (and which panes should be shown in the information +// window). The values are: +// 00: Opponents pane only (for a non-tournament game with no battle in progress) +// 01: Opponents and Rules panes (for a non-tournament game with a battle in progress) +// 02: Rules and bracket panes (for a tournament that has not yet started) +// 03: Opponents, Rules, and bracket panes (for a tournament match) +// 04: Spectators and Opponents pane (for a non-tournament spectator team) +// 05: Spectators, Opponents, Rules, and bracket panes (for a tournament spectator team) +// The 00, 01, and 04 cases don't really make sense, because the E1 command is more appropriate for inspecting +// non-tournament games. template struct S_TournamentGameDetailsBaseT_Ep3_E3 { @@ -3148,9 +2820,8 @@ struct S_TournamentGameDetailsBaseT_Ep3_E3 { // This field is used only if the bracket pane is shown /* 0054 */ parray bracket_entries; - // This field is used only if the Opponents pane is shown. If players_per_team - // is 2, all fields are shown; if player_per_team is 1, team_name and - // players[1] is ignored (only players[0] is shown). + // This field is used only if the Opponents pane is shown. If players_per_team is 2, all fields are shown; if + // players_per_team is 1, team_name and players[1] is ignored (only players[0] is shown). struct PlayerEntry { pstring name; pstring description; // Usually something like "RAmarl CLv24 E" @@ -3174,10 +2845,9 @@ check_struct_size(S_TournamentGameDetails_Ep3NTE_E3, 0x734); check_struct_size(S_TournamentGameDetails_Ep3_E3, 0x73C); // E3 (C->S): Player preview request (BB) -// header.flag is not used by the vanilla client, but newserv's MoreSaveSlots -// patch uses header.flag to tell the server how many save slots the client -// expects. The server uses this to send all the character previews at once, -// thus reducing the number of network roundtrips during login. +// header.flag is not used by the vanilla client, but newserv's MoreSaveSlots patch uses header.flag to tell the server +// how many save slots the client expects. The server uses this to send all the character previews at once, thus +// reducing the number of network roundtrips during login. struct C_PlayerPreviewRequest_BB_E3 { le_int32_t character_index = 0; @@ -3185,11 +2855,9 @@ struct C_PlayerPreviewRequest_BB_E3 { } __packed_ws__(C_PlayerPreviewRequest_BB_E3, 0x08); // E4: CARD lobby battle table state (Episode 3) -// When client sends an E4, server should respond with another E4 (but these -// commands have different formats). -// When the client has received an E4 command in which all entries have state 0 -// or 2, the client will stop the player from moving and show a message saying -// that the game will begin shortly. The server should send a 64 command shortly +// When client sends an E4, server should respond with another E4 (but these commands have different formats). +// When the client has received an E4 command in which all entries have state 0 or 2, the client will stop the player +// from moving and show a message saying that the game will begin shortly. The server should send a 64 command shortly // thereafter. // header.flag = seated state (1 = present, 0 = leaving) @@ -3202,10 +2870,10 @@ struct C_CardBattleTableState_Ep3_E4 { struct S_CardBattleTableState_Ep3_E4 { struct Entry { // State values: - // 0 = no player present - // 1 = player present, not confirmed - // 2 = player present, confirmed - // 3 = player present, declined + // 0 = no player present + // 1 = player present, not confirmed + // 2 = player present, confirmed + // 3 = player present, declined le_uint16_t state = 0; le_uint16_t unused = 0; le_uint32_t guild_card_number = 0; @@ -3235,9 +2903,8 @@ struct S_CardBattleTableConfirmation_Ep3_E5 { // E5 (S->C): Player preview (BB) // E5 (C->S): Create character or apply dressing room updates (BB) -// In the C->S case, the server can know whether it's a new character or -// dressing room updates based on the value of connection_phase from the 93 -// command. +// In the C->S case, the server can know whether it's a new character or dressing room updates based on the value of +// connection_phase from the 93 command. struct SC_PlayerPreview_CreateCharacter_BB_00E5 { le_int32_t character_index = 0; @@ -3246,11 +2913,11 @@ struct SC_PlayerPreview_CreateCharacter_BB_00E5 { // E6 (C->S): Spectator team control (Episode 3) -// With header.flag == 0, this command has no arguments and is used for -// requesting the spectator team list. The server responds with an E6 command. +// With header.flag == 0, this command has no arguments and is used for requesting the spectator team list. The server +// responds with an E6 command. -// With header.flag == 1, this command is used for joining a tournament -// spectator team. The following arguments are given in this form: +// With header.flag == 1, this command is used for joining a tournament spectator team. The following arguments are +// given in this form: struct C_JoinSpectatorTeam_Ep3_E6_Flag01 { le_uint32_t menu_id = 0; @@ -3261,33 +2928,31 @@ struct C_JoinSpectatorTeam_Ep3_E6_Flag01 { // Same format as 08 command. // E6 (S->C): Set guild card number and update client config (BB) -// 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). +// 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 { - // The error codes are (error_code => internal_error_code => string) - // 00 => (no error; client proceeds normally) - // => 01 => "(No901)\nUnable to obtain server address.\nPlease confirm your DNS settings.", - // => 02 => "(No902)\nNetwork initialization failed.\nPlease check your Internet connection settings.", - // 01 => 03 => "(No903)\nServer connection failed.\nThe server may be under maintenance.\nPlease check the current news updates on the Official Site.", - // 02 => 04 => "(No904)\nIncorrect Game ID or Game Password.", - // 03 => 05 => "(No905)\nIncorrect Game ID or Game Password.", - // 04 => 06 => "(No906)\nThis server is under maintenance.\nPlease see the Official Site for details.", - // (any) => 07 => "(No907)\nForced server disconnect.\nPlease check your Game ID and individual settings.", - // 06/07 => 08 => "(No910)\nThis Game ID has been suspended.", - // 05 => 09 => "(No911)\nThis Game ID is in use by another user.", - // 08 => 0A => "(No912)\nNo record for this Game ID.\nPlease register your user information at the Official Site.", - // 09 => 0B => "(No913)\nYour paid time has expired.\nPlease renew your account at the Official Site.", - // 0A => 0C => "(No914)\nDue to the program not being shut down properly, data is locked. Please try connecting again in 10 minutes.", - // 0B => 0D => "(No915)\nThis program has not been updated. The patch may not have run properly. Please try shutting down and restarting the program.\nThe most recent news updates can be found on the Official Site.", - // => 0E => "(No916)\nThis server is full.\nPlease try connecting again later." + // The error codes are (error_code => internal_error_code => string): + // 00 => (no error; client proceeds normally) + // => 01 => "(No901)\nUnable to obtain server address.\nPlease confirm your DNS settings.", + // => 02 => "(No902)\nNetwork initialization failed.\nPlease check your Internet connection settings.", + // 01 => 03 => "(No903)\nServer connection failed.\nThe server may be under maintenance.\nPlease check the current news updates on the Official Site.", + // 02 => 04 => "(No904)\nIncorrect Game ID or Game Password.", + // 03 => 05 => "(No905)\nIncorrect Game ID or Game Password.", + // 04 => 06 => "(No906)\nThis server is under maintenance.\nPlease see the Official Site for details.", + // (any) => 07 => "(No907)\nForced server disconnect.\nPlease check your Game ID and individual settings.", + // 06/07 => 08 => "(No910)\nThis Game ID has been suspended.", + // 05 => 09 => "(No911)\nThis Game ID is in use by another user.", + // 08 => 0A => "(No912)\nNo record for this Game ID.\nPlease register your user information at the Official Site.", + // 09 => 0B => "(No913)\nYour paid time has expired.\nPlease renew your account at the Official Site.", + // 0A => 0C => "(No914)\nDue to the program not being shut down properly, data is locked. Please try connecting again in 10 minutes.", + // 0B => 0D => "(No915)\nThis program has not been updated. The patch may not have run properly. Please try shutting down and restarting the program.\nThe most recent news updates can be found on the Official Site.", + // => 0E => "(No916)\nThis server is full.\nPlease try connecting again later." le_uint32_t error_code = 0; le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - // If security_token is zero, the client scrambles client_config before - // sending it back in a later 93 command. See scramble_bb_security_data in - // ReceiveCommands.cc for details on how this is done. + // If security_token is zero, the client scrambles client_config before sending it back in a later 93 command. See + // scramble_bb_security_data in ReceiveCommands.cc for details on how this is done. le_uint32_t security_token = 0; parray client_config; uint8_t can_create_team = 1; @@ -3296,9 +2961,8 @@ struct S_ClientInit_BB_00E6 { } __packed_ws__(S_ClientInit_BB_00E6, 0x3C); // E7 (C->S): Create spectator team (Episode 3) -// This command is used to create speectator teams for both tournaments and -// regular games. The server should be able to tell these cases apart by the -// menu and/or item ID. +// This command is used to create speectator teams for both tournaments and regular games. The server should be able to +// tell these cases apart by the menu and/or item ID. struct C_CreateSpectatorTeam_Ep3_E7 { le_uint32_t menu_id = 0; @@ -3322,8 +2986,8 @@ struct SC_SyncSaveFiles_BB_E7 { // E8 (S->C): Join spectator team (Episode 3) // header.flag = player count (including spectators) -// The client will crash if leader_id == client_id. Presumably one of the -// primary game's players should be the leader (this is what newserv does). +// The client will crash if leader_id == client_id. Presumably one of the primary game's players should be the leader +// (this is what newserv does). struct S_JoinSpectatorTeam_Ep3_E8 { /* 0000 */ Variations variations; // unused @@ -3346,11 +3010,9 @@ struct S_JoinSpectatorTeam_Ep3_E8 { /* 117C */ uint8_t episode = 0; /* 117D */ parray unused; struct SpectatorEntry { - // It seems that at some point Sega intended to show each player's rank in - // spectator teams. The unused1 and unused3 fields are intended for the - // player's encrypted rank text and rank color (according to old Sega logs), - // but the client ignores them. It's not clear what unused4 may have been - // for, but the client also completely ignores it. + // It seems that at some point Sega intended to show each player's rank in spectator teams. The unused1 and unused3 + // fields are intended for the player's encrypted rank text and rank color (according to old Sega logs), but the + // client ignores them. It's not clear what unused4 may have been for, but the client also completely ignores it. /* 00 */ le_uint32_t player_tag = 0; /* 04 */ le_uint32_t guild_card_number = 0; /* 08 */ pstring name; @@ -3363,13 +3025,11 @@ struct S_JoinSpectatorTeam_Ep3_E8 { /* 34 */ parray unused4; /* 38 */ } __packed_ws__(SpectatorEntry, 0x38); - // Somewhat misleadingly, this array also includes the players actually in the - // battle - they appear in the first positions. Presumably the first 4 are - // always for battlers, and the last 8 are always for spectators. + // Somewhat misleadingly, this array also includes the players actually in the battle - they appear in the first + // positions. Presumably the first 4 are always for battlers, and the last 8 are always for spectators. /* 1180 */ parray entries; /* 1420 */ pstring spectator_team_name; - // This field doesn't appear to be actually used by the game, but some servers - // send it anyway (and the game ignores it) + // This field doesn't appear to be actually used by the game, but Sega sent it anyway, so we do too. /* 1440 */ parray spectator_players; /* 3620 */ } __packed_ws__(S_JoinSpectatorTeam_Ep3_E8, 0x3620); @@ -3378,18 +3038,15 @@ struct S_JoinSpectatorTeam_Ep3_E8 { // 01E8 (C->S): Check guild card file checksum -// This struct is for documentation purposes only; newserv ignores the contents -// of this command. +// This struct is for documentation purposes only; newserv ignores the contents of this command. struct C_GuildCardChecksum_01E8 { le_uint32_t checksum = 0; le_uint32_t unused = 0; } __packed_ws__(C_GuildCardChecksum_01E8, 8); // 02E8 (S->C): Accept/decline guild card file checksum -// If needs_update is nonzero, the client will request the guild card file by -// sending an 03E8 command. If needs_update is zero, the client will skip -// downloading the guild card file and send an 04EB command (requesting the -// stream file) instead. +// If needs_update is nonzero, the client will request the guild card file by sending an 03E8 command. If needs_update +// is zero, the client will skip downloading the guild card file and send an 04EB command instead. struct S_GuildCardChecksumResponse_BB_02E8 { le_uint32_t needs_update = 0; @@ -3397,8 +3054,7 @@ struct S_GuildCardChecksumResponse_BB_02E8 { } __packed_ws__(S_GuildCardChecksumResponse_BB_02E8, 8); // 03E8 (C->S): Request guild card file -// No arguments -// Server should send the guild card file data using DC commands. +// No arguments. The server should send the guild card file data using DC commands. // 04E8 (C->S): Add guild card // Format is GuildCardBB (see PlayerSubordinates.hh) @@ -3410,15 +3066,16 @@ struct C_DeleteGuildCard_BB_05E8_08E8 { } __packed_ws__(C_DeleteGuildCard_BB_05E8_08E8, 4); // 06E8 (C->S): Update (overwrite) guild card -// Note: This command is also sent when the player writes a comment on their own -// guild card. -// Format is GuildCardBB (see PlayerSubordinates.hh) +// This command is also sent when the player writes a comment on their own guild card. +// Format is GuildCardBB (see PlayerSubordinates.hh). // 07E8 (C->S): Add blocked user -// Format is GuildCardBB (see PlayerSubordinates.hh) +// Format is GuildCardBB (see PlayerSubordinates.hh). This command only affects the save file; the client will send a +// C6 command separately to update the online blocked senders list. // 08E8 (C->S): Delete blocked user -// Same format as 05E8. +// Same format as 05E8. This command only affects the save file; the client will send a C6 command separately to update +// the online blocked senders list. // 09E8 (C->S): Write comment @@ -3435,19 +3092,15 @@ struct C_SwapGuildCardPositions_BB_0AE8 { } __packed_ws__(C_SwapGuildCardPositions_BB_0AE8, 8); // E9 (S->C): Remove player from spectator team (Episode 3) -// Same format as 66/69 commands. Like 69 (and unlike 66), the disable_udp field -// is unused in command E9. When a spectator leaves a spectator team, the -// primary players should receive a 6xB4x52 command to update their spectator -// counts. +// Same format as 66/69 commands. Like 69 (and unlike 66), the disable_udp field is unused in command E9. When a +// spectator leaves, the primary players should receive a 6xB4x52 command to update their spectator counts. // EA (S->C): Timed message box (Episode 3) -// The message appears in the upper half of the screen; the box is as wide as -// the 1A/D5 box but is vertically shorter. The box cannot be dismissed or -// interacted with by the player in any way; it disappears by itself after the -// given number of frames. -// header.flag appears to be relevant - the handler's behavior is different if -// it's 1 (vs. any other value). There don't seem to be any in-game behavioral -// differences though. +// The message appears in the upper half of the screen; the box is as wide as the 1A/D5 box but is vertically shorter. +// The box cannot be dismissed or interacted with by the player in any way; it disappears by itself after the given +// number of frames. +// If header.flag is 1, the message box closes all other lobby windows (e.g. the game list) and clears the lobby +// interaction mode. It does not stop the player from opening lobby windows again while the message box is up. struct S_TimedMessageBoxHeader_Ep3_EA { le_uint32_t duration = 0; // In frames; 30 frames = 1 second @@ -3464,14 +3117,14 @@ struct C_CreateTeam_BB_01EA { // 02EA (S->C): Create team result // No arguments except header.flag, which specifies the error code. Values: -// 0 = success -// 1 = generic error -// 2 = name already registered -// 3 = generic error -// 4 = generic error -// 5 = generic error -// 6 = generic error -// Anything else = command is ignored +// 0 = success +// 1 = generic error +// 2 = name already registered +// 3 = generic error +// 4 = generic error +// 5 = generic error +// 6 = generic error +// Anything else = command is ignored // 03EA (C->S): Add team member @@ -3481,24 +3134,22 @@ struct C_AddOrRemoveTeamMember_BB_03EA_05EA { // 04EA (S->C): Add team member result // No arguments except header.flag, which specifies the error code. Values: -// 0 = success -// 5 = team is full -// Anything else = generic error +// 0 = success +// 5 = team is full +// Anything else = generic error // 05EA (C->S): Remove team member // Same format as 03EA. // 06EA (S->C): Remove team member result -// No arguments except header.flag, which specifies the error code. 0 means -// success, but it's not known what any other values mean. The client expects -// the error code to be less than 7. +// No arguments except header.flag, which specifies the error code. 0 means success, but it's not known what any other +// values mean. The client expects the error code to be less than 7. // 07EA: Team chat struct SC_TeamChat_BB_07EA { pstring sender_name; - // Text follows here. The message is truncated by the client if it is longer - // than 0x8F wchar_ts. + // Text follows here. The message is truncated by the client if it is longer than 0x8F wchar_ts. } __packed_ws__(SC_TeamChat_BB_07EA, 0x20); // 08EA (C->S): Get team member list @@ -3520,9 +3171,8 @@ struct S_TeamMemberList_BB_09EA { } __packed_ws__(S_TeamMemberList_BB_09EA, 4); // 0CEA (S->C): Unknown -// The client ignores this command. It calls filter_curse_words on the -// presumably variable-sized text that follows the first 0x20 bytes of the -// command, but then does nothing with the result. +// The client ignores this command. It calls filter_curse_words on the presumably variable-sized text that follows the +// first 0x20 bytes of the command, but then does nothing with the result. struct S_Unknown_BB_0CEA { parray unknown_a1; @@ -3547,14 +3197,13 @@ struct C_SetTeamFlag_BB_0FEA { } __packed_ws__(C_SetTeamFlag_BB_0FEA, 0x800); // 10EA: Delete team (C->S) and result (S->C) -// No arguments (C->S) -// No arguments except header.flag (S->C) +// No arguments (C->S); no arguments except header.flag (S->C). // 11EA: Change team member privilege level -// The format below is used only when the client sends this command; when the -// server sends it, only header.flag is used. As with various other team -// commands, header.flag specifies the error code in this case. -// header.flag specifies the new privilege level for the specified team member. +// The format below is used only when the client sends this command; when the server sends it, only header.flag is +// used. As with various other team commands, header.flag specifies the error code in this case. + +// When sent by the client, header.flag specifies the new privilege level for the specified team member. // Known values: 0 = normal, 0x30 = leader, 0x40 = master struct C_ChangeTeamMemberPrivilegeLevel_BB_11EA { @@ -3565,8 +3214,7 @@ struct C_ChangeTeamMemberPrivilegeLevel_BB_11EA { // This updates the client's view of its system file. struct S_UpdateTeamMembership_BB_12EA { - // If skip_update_system_file is not zero, the client ignores the command. - // It's not clear why this field exists. + // If skip_update_system_file is not zero, the client ignores the command. It's not clear why this field exists. le_uint32_t skip_update_system_file = 0; PSOBBBaseTeamMembership membership; } __packed_ws__(S_UpdateTeamMembership_BB_12EA, 0x38); @@ -3575,8 +3223,8 @@ struct S_UpdateTeamMembership_BB_12EA { // header.flag specifies the number of entries. struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry { - // The client uses the first four fields of the membership to determine if - // the player is in a team: if any are nonzero, the player is in a team. + // The client uses the first four fields of the membership to determine if the player is in a team: if any are + // nonzero, the player is in a team. /* 0000 */ PSOBBBaseTeamMembership membership; /* 0034 */ le_uint32_t guild_card_number = 0; /* 0038 */ le_uint32_t lobby_client_id = 0; @@ -3589,12 +3237,10 @@ struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry { // No arguments. Client always sends 1 in the header.flag field. // 15EA (S->C): Team info for lobby players -// header.flag specifies the number of entries. The entry format appears to be -// the same as for the 13EA command. +// header.flag specifies the number of entries. The entry format appears to be the same as for the 13EA command. // 16EA (S->C): Transfer item via Simple Mail result -// No arguments except header.flag, which is zero if the transfer failed or -// nonzero if it succeeded. +// No arguments except header.flag, which is zero if the transfer failed or nonzero if it succeeded. // 18EA: Intra-team ranking information // No arguments (C->S) @@ -3636,8 +3282,7 @@ struct S_TeamRewardList_BB_19EA_1AEA { // Same format as 19EA. // 1BEA (C->S): Buy team reward -// No arguments except header.flag, which specifies a reward_id from a preceding -// 1AEA command. +// No arguments except header.flag, which specifies a reward_id from a preceding 1AEA command. // 1CEA: Cross-team ranking information // No arguments when sent by the client. @@ -3668,19 +3313,15 @@ struct C_RenameTeam_BB_1EEA { // This command behaves like 02EA, but is sent in response to 1EEA instead. // 20EA: Unknown -// header.flag is used, but no other arguments. When sent by the server, -// header.flag is an error code, similar to various other result commands in -// this section. +// header.flag is used, but no other arguments. When sent by the server, header.flag is an error code, similar to +// various other result commands in this section. // EB (S->C): Add player to spectator team (Episode 3) -// Same format and usage as 65 and 68 commands, but sent to spectators in a -// spectator team. -// This command is used to add both primary players and spectators - if the -// client ID in .lobby_data is 0-3, it's a primary player, otherwise it's a -// spectator. (In the case of a primary player joining, the other primary -// players in the game receive a 65 command rather than an EB command to notify -// them of the joining player; in the case of a joining spectator, the primary -// players receive a 6xB4x52 instead.) +// Same format and usage as 65 and 68 commands, but sent to spectators in a spectator team. +// This command is used to add both primary players and spectators - if the client ID in .lobby_data is 0-3, it's a +// primary player, otherwise it's a spectator. (In the case of a primary player joining, the other primary players in +// the game receive a 65 command rather than an EB command to notify them of the joining player; in the case of a +// joining spectator, the primary players receive a 6xB4x52 instead.) // 01EB (S->C): Send stream file index (BB) @@ -3703,8 +3344,7 @@ struct S_StreamFileChunk_BB_02EB { // header.flag is the chunk index. Server should respond with a 02EB command. // 04EB (C->S): Request stream file header -// No arguments -// Server should respond with a 01EB command. +// No arguments. The server should respond with a 01EB command. // EC (C->S): Create game (Episode 3) // Same format as C1; some fields are unused (e.g. episode, difficulty). @@ -3713,24 +3353,20 @@ struct S_StreamFileChunk_BB_02EB { struct C_LeaveCharacterSelect_BB_00EC { // Reason codes: - // 0 = canceled - // 1 = create or recreate character - // 2 = dressing room + // 0 = canceled + // 1 = create or recreate character + // 2 = dressing room le_uint32_t reason = 0; } __packed_ws__(C_LeaveCharacterSelect_BB_00EC, 4); // ED (S->C): Force leave lobby/game (Episode 3) -// No arguments -// This command forces the client out of the game or lobby they're currently in -// and sends them to the lobby. If the client is in a lobby (and not a game), -// the client sends a 98 in response as if they were in a game. Curiously, the -// client also sends a meseta transaction (BA) with a value of zero before -// sending an 84 to be added to a lobby. This is used when a spectator team is -// disbanded because the target game ends. +// No arguments. This command forces the client out of the game or lobby they're currently in and sends them to the +// lobby. This is used when a spectator team is disbanded because the target game ends. If the client is in a lobby +// (and not a game), the client sends a 98 in response as if they were in a game. Curiously, the client also sends a +// meseta transaction (BA) with a value of zero before sending an 84 to be added to a lobby. // ED (C->S): Update save file data (BB) -// There are several subcommands (noted in the structs below) that each update a -// specific kind of data. +// There are several subcommands (noted in the structs below) that each update a specific kind of data. struct C_UpdateOptionFlags_BB_01ED { le_uint32_t option_flags = 0; @@ -3768,9 +3404,8 @@ struct C_UpdateChallengeRecords_BB_08ED { } __packed_ws__(C_UpdateChallengeRecords_BB_08ED, 0x140); // EE: Trade cards (Episode 3) -// This command has different forms depending on the header.flag value; the flag -// values match the command numbers from the Episodes 1&2 trade window sequence. -// The sequence of events with the EE command also matches that of the Episodes +// This command has different forms depending on the header.flag value; the flag values match the command numbers from +// the Episodes 1&2 trade window sequence. The sequence of events with the EE command also matches that of the Episodes // 1&2 trade window; see the description of the D0 command above for details. // EE D0 (C->S): Begin trade @@ -3803,14 +3438,12 @@ struct S_CardTradeComplete_Ep3_EE_FlagD4 { } __packed_ws__(S_CardTradeComplete_Ep3_EE_FlagD4, 4); // EE (S->C): Scrolling message (BB) -// Same format as 01. The message appears at the top of the screen and slowly -// scrolls to the left. The maximum length of the message is 0x400 bytes (0x200 -// UTF-16 characters). +// Same format as 01. The message appears at the top of the screen and slowly scrolls to the left. The maximum length +// of the message is 0x400 bytes (0x200 UTF-16 characters). // EF (C->S): Join card auction (Episode 3) -// When a card auction is ready to begin, the leader sends this command to -// request the card list. The server then sends an EF command to all players -// to start the auction. +// When a card auction is ready to begin, the leader sends this command to request the card list. The server then sends +// an EF command to all players to start the auction. // EF (S->C): Start card auction (Episode 3) @@ -3825,16 +3458,13 @@ struct S_StartCardAuction_Ep3_EF { } __packed_ws__(S_StartCardAuction_Ep3_EF, 0x54); // EF (S->C): Set or disable shutdown command (BB) -// All variants of EF except 00EF cause the given Windows shell command to be -// run (via ShellExecuteA) just before the game exits normally. There can be at -// most one shutdown command at a time; a later EF command will overwrite the -// previous EF command's effects. The 00EF command deletes the previous shutdown -// command if any was present, causing no command to run when the game closes. -// There is no indication to the player when a shutdown command has been set. +// All variants of EF except 00EF cause the given Windows shell command to be run (via ShellExecuteA) just before the +// game exits normally. There can be at most one shutdown command at a time; a later EF command will overwrite the +// previous EF command's effects. The 00EF command deletes the previous shutdown command if any was present, causing no +// command to run when the game closes. There is no indication to the player when a shutdown command has been set. -// This command is likely just a vestigial debugging feature that Sega left in, -// but it presents a fairly obvious security risk. There is no way for the -// server to know whether an EF command it sent has actually executed on the +// This command is likely just a vestigial debugging feature that Sega left in, but it presents a fairly obvious +// security risk. There is no way for the server to know whether an EF command it sent has actually executed on the // client, so newserv's proxy unconditionally blocks this command. struct S_SetShutdownCommand_BB_01EF { @@ -3842,20 +3472,17 @@ struct S_SetShutdownCommand_BB_01EF { } __packed_ws__(S_SetShutdownCommand_BB_01EF, 0x200); // F0 (S->C): Force update player lobby data (BB) -// Format is PlayerLobbyDataBB (in PlayerSubordinates.hh). This command -// overwrites the lobby data for the player given by .client_id without -// reloading the game or lobby. This command is not valid on PSOBB Trial -// Edition. +// Format is PlayerLobbyDataBB (in PlayerSubordinates.hh). This command overwrites the lobby data for the player given +// by .client_id without reloading the game or lobby. This command is not valid on PSOBB Trial Edition. // This command probably exists to handle cases like the following: -// 1. Player A is in a team and is not the team master. Player A creates a game. -// 2. The master of the team changes to player B during this game. -// 3. Player B then joins the game that A is in. -// Some effects (e.g. Commander Blade) depend on the team master ID in the -// PlayerLobbyDataBB structure, and this is the only way to update that -// structure without reloading the lobby or game. If this command did not exist, -// then player A would not know that B was the master when they join the game, -// so A would not see the bonus from Commander Blade if B uses it. +// 1. Player A is in a team and is not the team master. Player A creates a game. +// 2. The master of the team changes to player B during this game. +// 3. Player B then joins the game that A is in. +// Some effects (e.g. Commander Blade) depend on the team master ID in the PlayerLobbyDataBB structure, and this is the +// only way to update that structure without reloading the lobby or game. If this command did not exist, then player A +// would not know that B was the master when they join the game, so A would not see the bonus from Commander Blade if B +// uses it. // F1: Invalid command // F2: Invalid command @@ -3875,68 +3502,54 @@ struct S_SetShutdownCommand_BB_01EF { // Removed commands -// There is evidence that some commands and features were fully removed from -// PSO at some point. +// There is evidence that some commands and features were fully removed from PSO at some point. -// There is a command named RcvGamePause in all DC versions of PSO, but its -// handler function is missing. It's likely there was a way to actually pause -// the game during early development, but it was removed, likely because it'd -// be a fairly poor player experience. +// There is a command named RcvGamePause in all DC versions of PSO, but its handler function is missing. It's likely +// there was a way to actually pause the game during early development, but it was removed, likely because it'd be a +// fairly poor player experience. -// There are two commands named SndGameStatus and SndGameCondition in the DC -// versions, but their sender functions are missing in all versions. It's not -// clear what exactly they would have sent, or when they would have been -// triggered. +// There are two commands named SndGameStatus and SndGameCondition in the DC versions, but their sender functions are +// missing in all versions. It's not clear what exactly they would have sent, or when they would have been triggered. +// They may have been an early implementation of the 6x6B-6x6F commands. -// Finally, there is a function named SndPsoGetText which was in DCv1 and DCv2, -// but not in DC NTE or the December 2000 prototype. This may have been a way -// for the server to prompt the user to input some text. As with the other -// unused functions, the code was removed, leaving only the function name. +// Finally, there is a function named SndPsoGetText which was in DCv1 and DCv2, but not in DC NTE or the December 2000 +// prototype. This may have been a way for the server to prompt the user to input some text. As with the other unused +// functions, the code was removed, leaving only the function name. -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// GAME SUBCOMMANDS //////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GAME SUBCOMMANDS /////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// The game subcommands are used in commands 60, 62, 6C, 6D, C9, and CB. These -// are laid out similarly as above. These structs start with G_ to indicate that -// they are (usually) bidirectional, and are (usually) generated by clients and -// consumed by clients. Generally in newserv source, these commands are referred -// to as (for example) 6x0B, etc., referencing the fact that they are almost -// always sent via a command starting with the hex digit 6. +// The game subcommands are used in commands 60, 62, 6C, 6D, C9, and CB. These are laid out similarly as above. These +// structs start with G_ to indicate that they are (usually) bidirectional, and are (usually) generated by clients and +// consumed by clients. Generally in newserv source, these commands are referred to as (for example) 6x0B, etc., +// referencing the fact that they are almost always sent via a command starting with the hex digit 6. // All game subcommands have the same header format, which is one of: // - XX SS ... // - XX 00 ?? ?? TT TT TT TT ... -// where X is the subcommand number (e.g. in 6xA2, it would be A2), S is the -// size in words of the entire subcommand (that is, overall size in bytes / 4), -// and T is the overall size in bytes. The second form is generally only used -// when the overall size in bytes is 0x400 or longer (so the S field doesn't -// suffice to describe its length), but it may also be used in some cases where -// the subcommand is shorter. -// Multiple subcommands may be sent in the same 6x command. It seems the client -// never sends commands like this, but newserv generates commands containing -// multiple subcommands in some situations (for example, the implementation of -// infinite HP does this). All versions of the client support this behavior. -// If any subcommand or group thereof is longer than 0x400 bytes, the 6C or 6D -// command must be used. The 60 and 62 commands exhibit undefined behavior if -// this limit is exceeded. +// where X is the subcommand number (e.g. in 6xA2, it would be A2), S is the size in words of the entire subcommand +// (that is, overall size in bytes / 4), and T is the overall size in bytes. The second form is generally only used +// when the overall size in bytes is 0x400 or longer (so the S field doesn't suffice to describe its length), but it +// may also be used in some cases where the subcommand is shorter. +// Multiple subcommands may be sent in the same 6x command. It seems the client never sends commands like this, but +// newserv generates commands containing multiple subcommands in some situations (for example, the implementation of +// $swsetall does this). All versions of the client support this behavior. +// If any subcommand or group thereof is longer than 0x400 bytes, the 6C or 6D command must be used. The 60 and 62 +// commands exhibit undefined behavior if this limit is exceeded. -// Many subcommands have different numbers on DC NTE and 11/2000, or simply -// don't exist on those versions. See the subcommand_definitions table in -// ReceiveSubcommands.cc for a full listing. +// Many subcommands have different numbers on DC NTE and 11/2000, or simply don't exist on those versions. See the +// subcommand_definitions table in ReceiveSubcommands.cc for a full listing. -// Some subcommands are "protected" on V3 and later (not including GC NTE); -// these commands are blocked by the client if they affect the local player. If -// a V3 or later client receives a protected subcommand that would affect its -// own player, it instead ignores the entire subcommand. This means that the -// server or other players cannot send these subcommands to affect other -// players; they can only send these commands to inform other clients about -// changes or actions from their own player. -// The protected subcommands are marked (protected) in the listings below. +// Some subcommands are "protected" on V3 and later (not including GC NTE); these commands are blocked by the client if +// they affect the local player. If a V3 or later client receives a protected subcommand that would affect its own +// player, it instead ignores the entire subcommand. This means that the server or other players cannot send these +// subcommands to affect other players; they can only send these commands to inform other clients about changes or +// actions from their own player. The protected subcommands are marked (protected) in the listings below. -// These common structures are used my many subcommands. +// These common structures are used by many subcommands. struct G_ClientIDHeader { uint8_t subcommand = 0; uint8_t size = 0; @@ -3945,7 +3558,7 @@ struct G_ClientIDHeader { struct G_EntityIDHeader { uint8_t subcommand = 0; uint8_t size = 0; - le_uint16_t entity_id = 0; // 0-B=client, 1000-3FFF=enemy, 4000-FFFF=object + le_uint16_t entity_id = 0; // [0, 0x0C] = client, [0x1000, 0x3FFF] = enemy, [0x4000, 0xFFFF] = object } __packed_ws__(G_EntityIDHeader, 4); struct G_ParameterHeader { uint8_t subcommand = 0; @@ -3969,10 +3582,9 @@ struct G_ExtendedHeaderT { // 6x02: Unknown // 6x03: Unknown -// These subcommands are completely ignored on V3 and later. -// On all known DC versions (NTE through V2), the contents of these commands -// are written to a global array, but nothing reads from this array. This -// command is likely a relic from pre-NTE development. +// These subcommands are completely ignored on V3 and later. On all known DC versions (NTE through V2), the contents of +// these commands are written to a global array, but nothing reads from this array. This command is likely a relic from +// pre-NTE development. struct G_Unknown_6x02_6x03 { G_ClientIDHeader header; @@ -3984,10 +3596,9 @@ struct G_Unknown_6x02_6x03 { } __packed_ws__(G_Unknown_6x02_6x03, 0x14); // 6x04: Activate switch by token (deprecated) -// This appears to be an early version of 6x05 or 6x0B; it only affects -// TObjDoorKey objects and can only activate them, not deactivate them. 6x05 -// and 6x0B are much more versatile and are used instead in all known versions -// of the game. This command is likely a relic from pre-NTE development. +// This appears to be an early version of 6x05 or 6x0B; it only affects TObjDoorKey objects and can only activate them, +// not deactivate them. 6x05 and 6x0B are much more versatile and are used instead in all known versions of the game. +// This command is likely a relic from pre-NTE development. struct G_LegacyActivateSwitchByToken_6x04 { G_ParameterHeader header; // param = door token (NOT entity ID or index) @@ -3996,36 +3607,30 @@ struct G_LegacyActivateSwitchByToken_6x04 { } __packed_ws__(G_LegacyActivateSwitchByToken_6x04, 8); // 6x05: Write switch flag -// Some things that don't look like switches are implemented as switches using -// this subcommand. For example, when all enemies in a room are defeated, this -// subcommand is used to unlock the doors. -// Note: In the client, this is a subclass of 6x04, similar to how 6xA2 is a -// subclass of 6x60. +// Some things that don't look like switches are implemented as switches using this subcommand. For example, when all +// enemies in a room are defeated, this subcommand is used to unlock the doors. +// Note: In the client, this is a subclass of 6x04, similar to how 6xA2 is a subclass of 6x60. struct G_WriteSwitchFlag_6x05 { - // header.entity_id may be 0xFFFF if no object is responsible for the switch - // flag state change - this happens when a wave event script sets a switch - // flag, for example. + // header.entity_id may be 0xFFFF if no object is responsible for the switch flag state change - this happens when a + // wave event script sets a switch flag, for example. G_EntityIDHeader header; - // It seems client_id isn't used anywhere. Some switch-like objects set it - // when sending this command, but it seems it's never read when 6x05 is - // received (including by virtual functions called on the affected object). - // PSO GC doesn't even bother to byteswap it, and we don't either since it's - // unused. + // It seems client_id isn't used anywhere. Some switch-like objects set it when sending this command, but it seems + // it's never read when 6x05 is received (including by virtual functions called on the affected object). PSO GC + // doesn't even bother to byteswap it, and we don't either since it's unused. le_uint16_t client_id = 0; le_uint16_t unused = 0; le_uint16_t switch_flag_num = 0; uint8_t switch_flag_floor = 0; // Only two bits in flags have meanings: - // 01 - set switch flag (if not set, the flag is cleared instead) - // 02 - play room unlock sound if floor matches client's floor + // 01: set switch flag (if not set, the flag is cleared instead) + // 02: play room unlock sound if floor matches client's floor uint8_t flags = 0; } __packed_ws__(G_WriteSwitchFlag_6x05, 0x0C); // 6x06: Send guild card -// On BB, the server is responsible for generating and sending the Guild Card -// data. newserv applies this logic for all versions of the game, to prevent -// players from sending Guild Cards other than their own. +// On BB, the server is responsible for generating and sending the Guild Card data. newserv applies this logic for all +// versions of the game, to prevent players from sending Guild Cards other than their own. struct G_SendGuildCard_DCNTE_6x06 { G_UnusedHeader header; @@ -4076,24 +3681,20 @@ struct G_SymbolChat_6x07 { // 6x08: Invalid subcommand // 6x09: Kill enemy (broken/unused) -// header.entity_id is expected to be an enemy ID, but is also expected to be -// in the range [0x00, 0x80) since the command handler writes to an array of -// 0x80 entries. This duality is nonsense because enemy IDs are greater than or -// equal to 0x1000, so any valid enemy ID would be far outside the array's -// range. newserv unconditionally blocks this command because it appears never -// to be used, and the array write is not bounds-checked, so it could be used -// to cause undefined behavior on other clients. It seems that this logic -// predates even DC NTE; it's likely that this was part of the implementation -// of enemy states before entity IDs and the standard 0xB50-entry array of -// states were introduced. +// header.entity_id is expected to be an enemy ID, but is also expected to be in the range [0x00, 0x80) since the +// command handler writes to an array of 0x80 entries. This duality is nonsense because enemy IDs are greater than or +// equal to 0x1000, so any valid enemy ID would be far outside the array's range. newserv unconditionally blocks this +// command because it appears never to be used, and the array write is not bounds-checked, so it could be used to cause +// undefined behavior on other clients. It seems that this logic predates even DC NTE; it's likely that this was part +// of the implementation of enemy states before enemy IDs in the tange [0x1000, 0x3FFF] and the standard 0xB50-entry +// array of states were introduced. struct G_LegacyKillEnemy_6x09 { G_EntityIDHeader header; } __packed_ws__(G_LegacyKillEnemy_6x09, 4); // 6x0A: Update enemy state -// In Ultimate mode, the low 6 bits of game_flags are ignored, and 6x9C is used -// to update those instead. +// In Ultimate mode, the low 6 bits of game_flags are ignored, and 6x9C is used to update those instead. template struct G_UpdateEnemyStateT_6x0A { @@ -4119,25 +3720,23 @@ struct G_UpdateObjectState_6x0B { struct G_AddStatusEffect_6x0C { G_ClientIDHeader header; - // Each status effect has an assigned slot; there are 5 slots and each slot - // may only hold one effect at a time. (The last slot, slot 4, is unused.) - // If a new status effect is added to a slot that already contains one, the - // existing status effect is replaced. Non-technique status effects have - // fixed or indefinite durations; technique-based effects have durations - // based on the technique's level. + // Each status effect has an assigned slot; there are 5 slots and each slot may only hold one effect at a time. (The + // last slot, slot 4, is unused.) If a new status effect is added to a slot that already contains one, the existing + // status effect is replaced. Non-technique status effects have fixed or indefinite durations; technique-based + // effects have durations based on the technique's level. // Values for effect_type: - // 02 = Freeze (slot 1; 5 seconds) - // 03 = Shock (slot 1; 10 seconds) - // 07 = Clears negative status effects (healing ring?) - // 09 = Shifta (slot 2; ((lv * 10) + 30) seconds; ATP (8.3 + 1.3 * lv)%) - // 0A = Deband (slot 3; ((lv * 10) + 30) seconds; DFP (8.3 + 1.3 * lv)%) - // 0B = Jellen (slot 2; ((lv * 10) + 30) seconds; ATP (8.3 + 1.3 * lv)%) - // 0C = Zalure (slot 3; ((lv * 10) + 30) seconds; DFP (8.3 + 1.3 * lv)%) - // 0F = Poison (slot 0) - // 10 = Paralysis (slot 0; 7 seconds for enemies, indefinite for players) - // 11 = Slow (slot 1; 7 seconds) - // 12 = Confuse (slot 1; 10 seconds) - // Anything else = command is ignored + // 02 = Freeze (slot 1; 5 seconds) + // 03 = Shock (slot 1; 10 seconds) + // 07 = Clears negative status effects (healing ring?) + // 09 = Shifta (slot 2; ((lv * 10) + 30) seconds; ATP (8.3 + 1.3 * lv)%) + // 0A = Deband (slot 3; ((lv * 10) + 30) seconds; DFP (8.3 + 1.3 * lv)%) + // 0B = Jellen (slot 2; ((lv * 10) + 30) seconds; ATP (8.3 + 1.3 * lv)%) + // 0C = Zalure (slot 3; ((lv * 10) + 30) seconds; DFP (8.3 + 1.3 * lv)%) + // 0F = Poison (slot 0) + // 10 = Paralysis (slot 0; 7 seconds for enemies, indefinite for players) + // 11 = Slow (slot 1; 7 seconds) + // 12 = Confuse (slot 1; 10 seconds) + // Anything else = command is ignored le_uint32_t effect_type = 0; le_float amount = 0; // Only used for Shifta/Deband/Jellen/Zalure } __packed_ws__(G_AddStatusEffect_6x0C, 0x0C); @@ -4223,8 +3822,7 @@ struct G_VolOptBossActions_6x16 { } __packed_ws__(G_VolOptBossActions_6x16, 0x0C); // 6x17: Set entity position and angle (not valid on Episode 3) -// This command sets an entity's position and angle without performing any -// validity checks, even on v3 and later. +// This command sets an entity's position and angle without performing any validity checks, even on v3 and later. struct G_SetEntityPositionAndAngle_6x17 { G_EntityIDHeader header; @@ -4251,8 +3849,7 @@ struct G_DarkFalzActions_6x19 { // 6x1A: Invalid subcommand -// 6x1B: Enable PK mode for player (not valid on Episode 3) (protected on GC -// NTE/V3/V4) +// 6x1B: Enable PK mode for player (not valid on Episode 3) (protected on GC NTE/V3/V4) struct G_EnablePKModeForPlayer_6x1B { G_ClientIDHeader header; @@ -4265,23 +3862,19 @@ struct G_DisablePKModeForPlayer_6x1C { } __packed_ws__(G_DisablePKModeForPlayer_6x1C, 4); // 6x1D: Request partial player data (pre-v1 only) -// The subcommand number 6x1D is not used in any final version of PSO; this -// number is assigned based on what the command number would be if it were. On -// DC NTE, this is subcommand 6x19; on 11/2000, it's 6x1B. -// This command does not appear to ever be sent by the client; however, it will -// respond with 6x1E if it receives this command. +// The subcommand number 6x1D is not used in any final version of PSO; this number is assigned based on what the +// command number would be if it were. On DC NTE, this is subcommand 6x19; on 11/2000, it's 6x1B. This command does not +// appear to ever be sent by the client; however, it will respond with 6x1E if it receives this command. struct G_RequestPartialPlayerData_DCProtos_6x1D { G_UnusedHeader header; } __packed_ws__(G_RequestPartialPlayerData_DCProtos_6x1D, 4); // 6x1E: Partial player data (pre-v1 only) -// The subcommand number 6x1E is not used in any final version of PSO; this -// number is assigned based on what the command number would be if it were. On -// DC NTE, this is subcommand 6x1A; on 11/2000, it's 6x1C. -// The command is truncated after the last valid item in the inventory (that -// is, there will be less than 0x360 bytes if the player has fewer than 30 -// items on hand). +// The subcommand number 6x1E is not used in any final version of PSO; this number is assigned based on what the +// command number would be if it were. On DC NTE, this is subcommand 6x1A; on 11/2000, it's 6x1C. +// The command is truncated after the last valid item in the inventory (that is, there will be less than 0x360 bytes if +// the player has fewer than 30 items on hand). struct G_PartialPlayerData_DCProtos_6x1E { /* 0000 */ G_ClientIDHeader header; @@ -4305,8 +3898,8 @@ struct G_SetPlayerFloor_6x1F { } __packed_ws__(G_SetPlayerFloor_6x1F, 8); // 6x20: Set position (protected on V3/V4) -// Existing clients send this in response to a 6x1F command when a new client -// joins a lobby or game, so the new client knows where to place them. +// Existing clients send this in response to a 6x1F command when a new client joins a lobby or game, so the new client +// knows where to place them. struct G_SetPosition_6x20 { G_ClientIDHeader header; @@ -4370,10 +3963,9 @@ struct G_FeedMag_6x28 { le_uint32_t fed_item_id = 0; } __packed_ws__(G_FeedMag_6x28, 0x0C); -// 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) -// (protected on GC NTE/V3 but not on V4) -// This subcommand is also used for reducing the size of stacks - if amount is -// less than the stack count, the item is not deleted and its ID remains valid. +// 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) (protected on GC NTE/V3 but not on V4) +// This subcommand is also used for reducing the size of stacks - if amount is less than the stack count, the item is +// not deleted and its ID remains valid. struct G_DeleteInventoryItem_6x29 { G_ClientIDHeader header; @@ -4407,9 +3999,8 @@ struct G_CreateInventoryItem_PC_V3_BB_6x2B : G_CreateInventoryItem_DC_6x2B { } __packed_ws__(G_CreateInventoryItem_PC_V3_BB_6x2B, 0x1C); // 6x2C: Impose hold (protected on GC NTE/V3/V4) -// This updates PlayerHoldState in the TObjPlayer struct, but the format is not -// the same. The names here match the fields in PlayerHoldState. A player hold -// prevents the player from moving further than the trigger radius from the +// This updates PlayerHoldState in the TObjPlayer struct, but the format is not the same. The names here match the +// fields in PlayerHoldState. A player hold prevents the player from moving further than the trigger radius from the // given x/z coordinates. struct G_ImposeHold_6x2C { @@ -4445,8 +4036,8 @@ struct G_ChangePlayerHP_6x2F { } __packed_ws__(G_ChangePlayerHP_6x2F, 0x0C); // 6x30: Change player level (protected on GC NTE/V3 but not V4) -// On DC NTE, the updated stats aren't sent, and the client may only gain a -// single level at once. On other versions, this is not the case. +// On DC NTE, the updated stats aren't sent, and the client may only gain a single level at once. On other versions, +// this is not the case. struct G_ChangePlayerLevel_DCNTE_6x30 { G_ClientIDHeader header; @@ -4488,12 +4079,10 @@ struct G_RevivePlayer_6x33 { // This subcommand is ignored by all versions of PSO. // 6x35: Unknown (pre-v1 only) -// This command seems to have a unique history. In DC NTE, it is 6x30, and has -// a handler that does something related to what 6x36 does (6x31 in DC NTE). In -// 11/2000, however, it seems to have been entirely deleted, and has no command -// number at all. But then in DC v1 and later, there is a gap in the subcommand -// number table, implying that this command was assigned a number, but there is -// no function to send or handle it. +// This command seems to have a unique history. In DC NTE, it is 6x30, and has a handler that does something related to +// what 6x36 does (6x31 in DC NTE). In 11/2000, however, it seems to have been entirely deleted, and has no command +// number at all. But then in DC v1 and later, there is a gap in the subcommand number table, implying that this +// command was assigned a number, but there is no function to send or handle it. struct G_Unknown_DCNTE_6x35 { G_ClientIDHeader header; @@ -4502,8 +4091,8 @@ struct G_Unknown_DCNTE_6x35 { // 6x36: Unknown (supported; game only) // This subcommand is completely ignored on V3. -// TODO: It is not ignored on V2, and presumably earlier versions as well. -// Figure out what it does. It's something related to PBs. +// TODO: It is not ignored on V2, and presumably earlier versions as well. Figure out what it does. It's something +// related to PBs. struct G_Unknown_6x36 { // The parameter seems to be expected to be in the range [0, 12). @@ -4526,11 +4115,9 @@ struct G_DonateToPhotonBlast_6x38 { le_uint16_t unused = 0; } __packed_ws__(G_DonateToPhotonBlast_6x38, 8); -// 6x38.5 (nominally) / 6x33 (DC NTE) / 6x35 (11/2000): Unknown (related to -// level up sequence) -// This command was deleted after 11/2000 and has no assigned number in any -// final version of PSO, hence the odd numbering. It is sent during the level -// up sequence in certain situations (TODO). +// 6x38.5 (nominally) / 6x33 (DC NTE) / 6x35 (11/2000): Unknown (related to level up sequence) +// This command was deleted after 11/2000 and has no assigned number in any final version of PSO, hence the odd +// numbering. It is sent during the level up sequence in certain situations (TODO). struct G_UnknownLevelUpSequence_DCNTE_6x33_112000_6x35 { G_ClientIDHeader header; @@ -4544,8 +4131,8 @@ struct G_SetPhotonBlastReadyFlag_6x39 { } __packed_ws__(G_SetPhotonBlastReadyFlag_6x39, 4); // 6x3A: Clear photon blast ready flag (protected on V3/V4) -// This is sent when a player's PB meter drops below 100. This can happen if -// a player donates to a PB instead of joining it. +// This is sent when a player's PB meter drops below 100. This can happen if a player donates to a PB instead of +// joining it. struct G_ClearPhotonBlastReadyFlag_6x3A { G_ClientIDHeader header; @@ -4562,11 +4149,9 @@ struct G_ClearTemporaryPhotonBlastStateFlags_6x3B { // This command has a handler, but it does nothing, even on DC NTE. // 6x3D: Target list base -// This appears to be a base class for 6x46, 6x47, and 6x49 (and possibly other -// subcommands), but it is never sent on the wire. Its likely purpose is to -// provide the TargetEntry structure and related functions to derived classes, -// but it does still have a subcommand number and a structure of its own, as -// described here. +// This appears to be a base class for 6x46, 6x47, and 6x49 (and possibly other subcommands), but it is never sent on +// the wire. Its likely purpose is to provide the TargetEntry structure and related functions to derived classes, but +// it does still have a subcommand number and a structure of its own, as described here. struct TargetEntry { le_uint16_t entity_id = 0; @@ -4610,10 +4195,8 @@ struct G_WalkToPosition_6x40 { // 6x41: Move to position (v1) // 6x42: Run (protected on GC NTE/V3/V4) -// Command 6x41 is completely ignored by v2 and later. -// If UDP mode is enabled, this command is sent via UDP. -// TODO: Should newserv translate 6x41 to 6x42? Is there any difference in how -// v1 and pre-v1 handle 6x42 vs. 6x41? +// Command 6x41 is completely ignored by v2 and later. If UDP mode is enabled, this command is sent via UDP. +// TODO: Should newserv translate 6x41 to 6x42? Is there any difference in how v1 and pre-v1 handle 6x42 vs. 6x41? struct G_MoveToPosition_6x41_6x42 { G_ClientIDHeader header; @@ -4631,12 +4214,10 @@ struct G_Attack_6x43_6x44_6x45 { le_uint16_t unknown_a2 = 0; } __packed_ws__(G_Attack_6x43_6x44_6x45, 8); -// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on GC -// NTE/V3/V4) -// The number of targets is not bounds-checked during byteswapping on GC -// clients. The client only expects up to 10 entries here, so if the number of -// targets is too large, the client will byteswap the function's return address -// on the stack, and it will crash. +// 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on GC NTE/V3/V4) +// The number of targets is not bounds-checked during byteswapping on GC clients. The client only expects up to 10 +// entries here, so if the number of targets is too large, the client will byteswap the function's return address on +// the stack, and it will crash. struct G_AttackFinished_Header_6x46 { G_ClientIDHeader header; @@ -4651,11 +4232,9 @@ struct G_CastTechnique_Header_6x47 { G_ClientIDHeader header; uint8_t technique_number = 0; uint8_t unused = 0; // Must not be negative - // Note: The level here isn't the actual tech level that was cast, if the - // actual level is > 15. In that case, a 6x8D is sent first, which contains - // the additional level which is added to this level at cast time. Sega - // probably did this as part of implementing v1/v2 compatibility and never - // cleaned it up. + // Note: The level here isn't the actual tech level that was cast, if the actual level is > 15. In that case, a 6x8D + // is sent first, which contains the additional level which is added to this level at cast time. Sega probably did + // this as part of implementing v1/v2 compatibility and never cleaned it up. uint8_t level = 0; uint8_t target_count = 0; // Must be in [0, 10] // Up to 10 TargetEntries are sent here @@ -4666,8 +4245,7 @@ struct G_CastTechnique_Header_6x47 { struct G_CastTechniqueComplete_6x48 { G_ClientIDHeader header; le_uint16_t technique_number = 0; - // This level matches the level sent in the 6x47 command, even if that level - // was overridden by a preceding 6x8D command. + // This level matches what's sent in the 6x47 command, even if that level was overridden by a preceding 6x8D command. le_uint16_t level = 0; } __packed_ws__(G_CastTechniqueComplete_6x48, 8); @@ -4708,8 +4286,7 @@ struct G_PlayerDied_6x4D { } __packed_ws__(G_PlayerDied_6x4D, 8); // 6x4E: Player can be revived (protected on GC NTE/V3/V4) -// This command creates the particle effect that Reverser and Moon Atomizers -// can target. +// This command creates the particle effect that Reverser and Moon Atomizers can target. struct G_PlayerRevivable_6x4E { G_ClientIDHeader header; @@ -4722,9 +4299,8 @@ struct G_PlayerRevived_6x4F { } __packed_ws__(G_PlayerRevived_6x4F, 4); // 6x50: Switch interaction (protected on V3/V4) -// If UDP mode is enabled, this command is sent via UDP. This command doesn't -// actually do anything with the switch; it just sets the player's animation -// state. 6x05 is used to set the switch flag if needed. +// If UDP mode is enabled, this command is sent via UDP. This command doesn't actually do anything with the switch; it +// just sets the player's animation state. 6x05 is used to set the switch flag if needed. struct G_SwitchInteraction_6x50 { G_ClientIDHeader header; @@ -4732,10 +4308,9 @@ struct G_SwitchInteraction_6x50 { } __packed_ws__(G_SwitchInteraction_6x50, 8); // 6x51: Set player angle -// If UDP mode is enabled, this command is sent via UDP. -// This command appears to be vestigial - no version of the game has a handler -// for it (it is always ignored), but most versions have a function that sends -// it. It's not known if this function is ever called, or how to trigger it. +// If UDP mode is enabled, this command is sent via UDP. This command appears to be vestigial - no version of the game +// has a handler for it (it is always ignored), but most versions have a function that sends it. It's not known if this +// function is ever called, or how to trigger it. struct G_SetPlayerAngle_6x51 { G_ClientIDHeader header; @@ -4759,9 +4334,9 @@ struct G_Unknown_6x53 { } __packed_ws__(G_Unknown_6x53, 4); // 6x54: Unknown -// This subcommand is completely ignored by DCv2 and later. On DC NTE, 11/2000, -// and DCv1, the handler has some logic in it and it calls a virtual function -// on TObjPlayer, but that codepath ends up doing nothing. +// This subcommand is completely ignored by DCv2 and later. On DC NTE, 11/2000, and DCv1, the handler has some logic in +// it and it calls a virtual function on TObjPlayer, but that codepath ends up doing nothing. +// TODO: Could it struct G_Unknown_6x54 { G_ClientIDHeader header; @@ -4821,9 +4396,8 @@ struct G_PickUpItemRequest_6x5A { // This command has a handler, but it does nothing, even on DC NTE. // 6x5C: Destroy floor item -// Same format as 6x63. It appears this command should not be used because it -// removes the item from the floor just like 6x63 does, but 6x5C doesn't call -// the item's destructor. +// Same format as 6x63. It appears this command should not be used because it removes the item from the floor just like +// 6x63 does, but 6x5C doesn't call the item's destructor. // 6x5D: Drop meseta or stacked item @@ -4854,9 +4428,8 @@ struct FloorItem { /* 02 */ le_uint16_t entity_index = 0; // < 0x0B50 if source_type == 1; otherwise < 0x0BA0 /* 04 */ VectorXZF pos; /* 0C */ le_uint16_t room_id = 0; - // The drop number is scoped to the floor and increments by 1 each time an - // item is dropped. The last item dropped in each floor has drop_number equal - // to total_items_dropped_per_floor[floor - 1] - 1. + // The drop number is scoped to the floor and increments by 1 each time an item is dropped. The last item dropped in + // each floor has drop_number equal to total_items_dropped_per_floor[floor - 1] - 1. /* 0E */ le_uint16_t drop_number = 0; /* 10 */ ItemData item; /* 24 */ @@ -4891,8 +4464,7 @@ struct G_StandardDropItemRequest_PC_V3_BB_6x60 : G_StandardDropItemRequest_DC_6x } __packed_ws__(G_StandardDropItemRequest_PC_V3_BB_6x60, 0x18); // 6x61: Activate item effect -// This is used for MAG effects like invulnerability, but is also used for some -// weapons' specials (e.g. Heaven Punisher). +// This is used for MAG effects like invulnerability, but is also used for some specials (e.g. Heaven Punisher). struct G_ActivateMagEffect_6x61 { G_UnusedHeader header; @@ -4904,8 +4476,8 @@ struct G_ActivateMagEffect_6x61 { // This command has a handler, but it does nothing even on DC NTE. // 6x63: Destroy floor item -// This is sent by the leader to destroy a floor item when there are 50 or more -// items already on the ground on the current floor. +// This is sent by the leader to destroy a floor item when there are 50 or more items already on the ground on the +// current floor. struct G_DestroyFloorItem_6x5C_6x63 { G_UnusedHeader header; @@ -4929,8 +4501,7 @@ struct G_UseStarAtomizer_6x66 { // 6x67: Trigger set event struct G_TriggerSetEvent_6x67 { - // If this command is sent by a box that triggers a set event, the entity ID - // in the header is the box's object ID. + // If this command is sent by a box that triggers a set event, the entity ID in the header is the box's object ID. G_EntityIDHeader header; le_uint32_t floor = 0; le_uint32_t event_id = 0; // NOT event index @@ -4948,22 +4519,16 @@ struct G_SetTelepipeState_6x68 { // Note: NPCs cannot be destroyed with 6x69; 6x1C is used instead for that. // For commands 0 and 3, the template indexes are: -// 0: NOL 1: CICIL 2: CICIL 3: MARACA 4: ELLY -// 5: SHINO 6: DONOPH 7: MOME 8: ALICIA 9: ASH -// 10: ASH 11: SUE 12: KIREEK 13: BERNIE 14: GILLIAM -// 15: ELENOR 16: ALICIA 17: MONTAGUE 18: RUPIKA 19: MATHA -// 20: ANNA 21: TONZLAR 22: TOBOKKE 23: GEKIGASKY 24: TYPE:O -// 25: TYPE:W 26: GIZEL 27: DACCI 28: HOPKINS 29: DORONBO -// 30: KROE 31: MUJO 32: RACTON 33: LIONEL 34: ZOKE -// 35: SUE 36: NADJA 37: ELENOR 38: KIREEK 39: BERNIE -// 40: CHRIS 41: RENEE 42: KAREN 43: BEIRON 44: NAKA -// 45: LEO 46: HOUND 47: MADELEINE 48: VALLETTA 49: BOGARDE -// 50: ULT 51: TYPE:I 52: TYPE:V 53: TACHIBANA 54: OSMAN -// 55: VIVIENNE 56: BP 57: SHINTARO 58: KEN 59: TAKUYA -// 60: SOKON 61: UKON 62: CANTONA 63: HASE -// Created NPCs have a base level according to their template index. On Hard, -// 25 is added to their base level; on Very Hard, 50 is added; on Ultimate, 150 -// is added. In all cases the NPC's level is clamped to [1, 199]. +// 0: NOL 1: CICIL 2: CICIL 3: MARACA 4: ELLY 5: SHINO 6: DONOPH 7: MOME +// 8: ALICIA 9: ASH 10: ASH 11: SUE 12: KIREEK 13: BERNIE 14: GILLIAM 15: ELENOR +// 16: ALICIA 17: MONTAGUE 18: RUPIKA 19: MATHA 20: ANNA 21: TONZLAR 22: TOBOKKE 23: GEKIGASKY +// 24: TYPE:O 25: TYPE:W 26: GIZEL 27: DACCI 28: HOPKINS 29: DORONBO 30: KROE 31: MUJO +// 32: RACTON 33: LIONEL 34: ZOKE 35: SUE 36: NADJA 37: ELENOR 38: KIREEK 39: BERNIE +// 40: CHRIS 41: RENEE 42: KAREN 43: BEIRON 44: NAKA 45: LEO 46: HOUND 47: MADELEINE +// 48: VALLETTA 49: BOGARDE 50: ULT 51: TYPE:I 52: TYPE:V 53: TACHIBANA 54: OSMAN 55: VIVIENNE +// 56: BP 57: SHINTARO 58: KEN 59: TAKUYA 60: SOKON 61: UKON 62: CANTONA 63: HASE +// Created NPCs have a base level according to their template index. On Hard, 25 is added to their base level; on Very +// Hard, 50 is added; on Ultimate, 150 is added. In all cases the NPC's level is clamped to [1, 199]. struct G_NPCControl_6x69 { G_UnusedHeader header; @@ -4983,10 +4548,9 @@ struct G_SetBossWarpFlags_6x6A { // 6x6B: Sync enemy state (used while loading into game) -// Note that DC NTE doesn't send the decompressed size in the header here. This -// is a bug that can cause the client to write more entries than it should when -// it receives one of these commands. All later versions, including 11/2000, -// use the second header structure here, which prevents this issue. +// Note that DC NTE doesn't send the decompressed size in the header here. This is a bug that can cause the client to +// write more entries than it should when it receives one of these commands. All later versions, including 11/2000, use +// the second header structure here, which prevents this issue. struct G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E { G_ExtendedHeaderT header; @@ -5004,44 +4568,34 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E { // Decompressed format is a list of SyncEnemyStateEntry (from Map.hh). // 6x6C: Sync object state (used while loading into game) -// Compressed format is the same as 6x6B. -// Decompressed format is a list of SyncObjectStateEntry (from Map.hh). +// Compressed format is the same as 6x6B; decompressed format is a list of SyncObjectStateEntry (from Map.hh). // 6x6D: Sync item state (used while loading into game) // Internal name: RcvItemCondition // Compressed format is the same as 6x6B. -// There is a bug in the client that can cause desync between players' item IDs -// if the 6x6D command is sent too quickly. Under normal operation, the client -// keeps track of the next item ID to be assigned for items it creates, and uses -// that to assign item IDs to its inventory items when it's done loading (just -// before it sends the 6F command). The loading process triggered by the 64 -// (game join) command resets these next item ID variables to their default -// values, and the 6x6D command sent by the leader resets them again to match -// the corresponding variables in the leader's item state. However, the loading -// process doesn't actually start until the next frame after the 64 command is -// received, so if the 6x6D command is received on the same frame as the 64 -// command, it will set the next item ID variables correctly, and the loading -// process will then clear all of them on the next frame. The client will then -// assign its own inventory item IDs based on the default base item ID, which -// will result in incorrect IDs if another player had previously been in the -// game in the same slot (since the leader's next item ID for the joining player -// will not match the default value). Fortunately, the game processes commands -// in two phases: first, it receives as much data as possible, then it processes -// as many commands as possible. So, to prevent this bug, we delay all commands -// after a 64 is sent until the client responds to a ping command (sent -// immediately after the 64 command), which ensures that the 64 and 6x6D -// commands cannot be processed on the same frame. +// There is a bug in the client that can cause desync between players' item IDs if the 6x6D command is sent too +// quickly. Under normal operation, the client keeps track of the next item ID to be assigned for items it creates, and +// uses that to assign item IDs to its inventory items when it's done loading (just before it sends the 6F command). +// The loading process triggered by the 64 (game join) command resets these next item ID variables to their default +// values, and the 6x6D command sent by the leader resets them again to match the corresponding variables in the +// leader's item state. However, the loading process doesn't actually start until the next frame after the 64 command +// is received, so if the 6x6D command is received on the same frame as the 64 command, it will set the next item ID +// variables correctly, and the loading process will then clear all of them on the next frame. The client will then +// assign its own inventory item IDs based on the default base item ID, which will result in incorrect IDs if another +// player had previously been in the game in the same slot (since the leader's next item ID for the joining player will +// not match the default value). Fortunately, the game processes commands in two phases: first, it receives as much +// data as possible, then it processes as many commands as possible. So, to prevent this bug, we delay all commands +// after a 64 is sent until the client responds to a ping command (sent immediately after the 64 command), which +// ensures that the 64 and 6x6D commands cannot be processed on the same frame. struct G_SyncItemState_6x6D_Decompressed { - // Note: 16 vs. 15 is not a bug here - there really is an extra field in the - // next drop number vs. the floor item count. Despite this, Pioneer 2 or Lab - // (floor 0) isn't included in next_drop_number_per_floor (so Forest 1 is [0] - // in that array) but it is included in floor_item_count_per_floor (so Forest - // 1 is [1] there). + // Note: 16 vs. 15 is not a bug here - there really is an extra field in the next drop number vs. the floor item + // count. Despite this, Pioneer 2 or Lab (floor 0) isn't included in next_drop_number_per_floor (so Forest 1 is [0] + // in that array) but it is included in floor_item_count_per_floor (so Forest 1 is [1] there). /* 00 */ parray next_drop_number_per_floor; - // Only [0]-[3] in this array are ever actually used in normal gameplay, but - // the client fills in all 12 of these with reasonable values. + // Only [0]-[3] in this array are ever actually used in normal gameplay, but the client fills in all 12 of these with + // reasonable values. /* 20 */ parray next_item_id_per_player; /* 50 */ parray floor_item_count_per_floor; // Variable-length field: @@ -5059,7 +4613,7 @@ struct G_SyncSetFlagState_6x6E_Decompressed { // Variable-length fields follow here: // EntitySetFlags entity_set_flags; // Total size is entity_set_flags_size // le_uint16_t event_set_flags[event_set_flags_size / 2]; // Same order as in map files (NOT sorted by event_id) - // SwitchFlags switch_flags; // 0x200 bytes (0x10 floors) on v1 and earlier; 0x240 bytes (0x12 floors) on v2 and later + // SwitchFlags switch_flags; // 0x200 bytes (16 floors) on v1 and earlier; 0x240 bytes (18 floors) on v2 and later struct EntitySetFlags { le_uint32_t object_set_flags_offset = 0; @@ -5073,9 +4627,8 @@ struct G_SyncSetFlagState_6x6E_Decompressed { } __packed_ws__(G_SyncSetFlagState_6x6E_Decompressed, 8); // 6x6F: Set quest flags (used while loading into game) -// On Episode 3, this command sets the seq vars instead. However, the client -// never sends this, since seq vars don't need to be synced to the entire game -// in online play. +// On Episode 3, this command sets the seq vars instead. However, the client never sends this, since seq vars don't +// need to be synced to the entire game in online play. struct G_SetQuestFlags_DCv1_6x6F { G_UnusedHeader header; @@ -5095,16 +4648,14 @@ struct G_SetSeqVars_Ep3_6x6F { struct G_SetQuestFlags_BB_6x6F { G_UnusedHeader header; QuestFlags quest_flags; - // If use_apply_mask is 1, only the flags set in BB_QUEST_FLAG_APPLY_MASK - // (in PlayerSubordinates.cc) are overwritten on the receiving client's end. - // The client always sends this with use_apply_mask = 1. + // If use_apply_mask is 1, only the flags set in BB_QUEST_FLAG_APPLY_MASK (in PlayerSubordinates.cc) are overwritten + // on the receiving client's end. The client always sends this with use_apply_mask = 1. le_uint32_t use_apply_mask = 1; } __packed_ws__(G_SetQuestFlags_BB_6x6F, 0x208); // 6x70: Sync player disp data and inventory (used while loading into game) -// Annoyingly, they didn't use the same format as the 65/67/68 commands here, -// and instead rearranged a bunch of things. This is presumably because this -// structure also includes transient state (e.g. current HP). +// Annoyingly, they didn't use the same format as the 65/67/68 commands here, and instead rearranged a bunch of things. +// This is presumably because this structure also includes transient state (e.g. current HP). struct G_6x70_Sub_Telepipe { /* 00 */ TelepipeState state; @@ -5248,16 +4799,16 @@ struct G_DoneLoadingIntoGame_6x72 { } __packed_ws__(G_DoneLoadingIntoGame_6x72, 4); // 6x73: Exit quest -// This command misbehaves if sent in a lobby or in a game when no quest is -// loaded. +// This command misbehaves if sent in a lobby or in a game when no quest is loaded. newserv includes a client function +// named ExitAnywhere that performs the same function when no quest is loaded. struct G_ExitQuest_6x73 { G_UnusedHeader header; } __packed_ws__(G_ExitQuest_6x73, 4); // 6x74: Word select -// There is a bug in PSO GC with regard to this command: the client does not -// byteswap the header, which means the client_id field is big-endian. +// There is a bug in PSO GC with regard to this command: the client does not byteswap the header, which means the +// client_id field is big-endian. template struct G_WordSelectT_6x74 { @@ -5286,8 +4837,8 @@ struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 { } __packed_ws__(G_UpdateQuestFlag_V3_BB_6x75, 0x0C); // 6x76: Set entity set flags -// This command can only be used to set set flags, since the game performs a -// bitwise OR operation instead of a simple assignment. +// This command can only be used to set set flags, since the game performs a bitwise OR operation instead of a simple +// assignment. struct G_SetEntitySetFlags_6x76 { G_EntityIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object @@ -5296,8 +4847,7 @@ struct G_SetEntitySetFlags_6x76 { } __packed_ws__(G_SetEntitySetFlags_6x76, 8); // 6x77: Sync quest register -// This is sent by the client when an opcode D9 is executed within a quest. -// This command does nothing on Episode 3. +// This is sent by the client when an opcode D9 is executed within a quest. This command does nothing on Episode 3. struct G_SyncQuestRegister_6x77 { G_UnusedHeader header; @@ -5310,10 +4860,9 @@ struct G_SyncQuestRegister_6x77 { } __packed_ws__(G_SyncQuestRegister_6x77, 0x0C); // 6x78: Unknown -// This command appears to set a per-player timer of some sort. It was -// introduced in v2, and there is an object that uses this timer, but it seems -// that that object is never constructed. In v3 and later, that object has been -// entirely deleted, so the command does nothing. +// This command appears to set a per-player timer of some sort. It was introduced in v2, and there is an object that +// uses this timer, but it seems that that object is never constructed. In v3 and later, that object has been entirely +// deleted, so the command does nothing. struct G_Unknown_6x78 { G_UnusedHeader header; @@ -5371,13 +4920,13 @@ struct G_SetChallengeRecords_BB_6x7C : G_SetChallengeRecordsBase_6x7C { struct G_SetBattleModeData_6x7D { G_UnusedHeader header; // Values for what (0-6; values 7 and above are not valid): - // 0 = Unknown (params[0] and [1] are used) - // 2 = Unknown (no params are used) - // 3 = Set player score (params[0] = client ID, [1] = score) - // 4 = Unknown (params[0] = client ID) - // 5 = Unknown (no params are used) - // 6 = Unknown (all params are used) - // Anything else = command is ignored + // 0 = Unknown (params[0] and [1] are used) + // 2 = Unknown (no params are used) + // 3 = Set player score (params[0] = client ID, [1] = score) + // 4 = Unknown (params[0] = client ID) + // 5 = Unknown (no params are used) + // 6 = Unknown (all params are used) + // Anything else = command is ignored uint8_t what = 0; uint8_t unknown_a1 = 0; // Only used when what == 0 uint8_t unused = 0; @@ -5409,15 +4958,12 @@ check_struct_size(G_BattleScoresBE_6x7F, 0x24); struct G_TriggerTrap_6x80 { G_ClientIDHeader header; - // Traps set by players are numbered according to their type and who set - // them. The trap number is (client_id * 80) + (trap_type * 20) + trap_index. - // trap_index comes from the 6x83 command, described below. - // Note that the trap number does not directly correspond to a specific - // object ID. Instead, object IDs past the end of the map data are - // dynamically allocated when players place traps. - // TODO: What happens in the case of data races, e.g. if two players set - // traps at the same time? Does the game just desync because they get - // different object IDs on different clients? + // Traps set by players are numbered according to their type and who set them. The trap number is (client_id * 80) + + // (trap_type * 20) + trap_index. trap_index comes from the 6x83 command, described below. Note that the trap number + // does not directly correspond to a specific object ID; instead, object IDs past the end of the map data are + // dynamically allocated when players place traps. + // TODO: What happens in the case of data races, e.g. if two players set traps at the same time? Does the game just + // desync because they get different object IDs on different clients? le_uint16_t trap_number = 0; le_uint16_t what = 0; // Must be 0, 1, or 2 } __packed_ws__(G_TriggerTrap_6x80, 8); @@ -5491,19 +5037,18 @@ struct G_SetKillerEntityID_6x89 { } __packed_ws__(G_SetKillerEntityID_6x89, 8); // 6x8A: Show Challenge time records window (not valid on Episode 3) -// The leader sends this command to tell other clients to show, hide, or update -// the window that shows Challenge Mode stage competion and time records during -// Challenge quest selection. +// The leader sends this command to tell other clients to show, hide, or update the window that shows Challenge Mode +// stage competion and time records during Challenge quest selection. struct G_ShowChallengeTimeRecordsWindow_6x8A { G_ClientIDHeader header; // Values for which (decimal): - // 0 = hide window - // 1 = show Episode 1 completion state per player - // 2 = show Episode 2 completion state per player - // 3-11 = show times for Episode 1 stage (which - 2) - // 12-16 = show times for Episode 2 stage (which - 11) - // Anything else = command is ignored + // 0 = hide window + // 1 = show Episode 1 completion state per player + // 2 = show Episode 2 completion state per player + // 3-11 = show times for Episode 1 stage (which - 2) + // 12-16 = show times for Episode 2 stage (which - 11) + // Anything else = command is ignored le_uint32_t which = 0; } __packed_ws__(G_ShowChallengeTimeRecordsWindow_6x8A, 8); @@ -5514,8 +5059,8 @@ struct G_ShowChallengeTimeRecordsWindow_6x8A { // This command has a handler, but it does nothing. // 6x8D: Set technique level override (protected on V3/V4) -// This command is sent immediately before 6x47 if the technique level is above -// 15. Presumably this was done for compatibility between v1 and v2. +// This command is sent immediately before 6x47 if the technique level is above 15. Presumably this was done for +// compatibility between v1 and v2. struct G_SetTechniqueLevelOverride_6x8D { G_ClientIDHeader header; @@ -5554,8 +5099,8 @@ struct G_UpdateAttackableColState_6x91 : G_UpdateObjectState_6x0B { } __packed_ws__(G_UpdateAttackableColState_6x91, 0x14); // 6x92: Unknown (not valid on Episode 3) -// TODO: It looks like this sets the heights of players in the online victory -// screen? Figure out if this is actually what it does. +// TODO: It looks like this sets the heights of players in the online victory screen? Figure out if this is actually +// what it does. struct G_Unknown_6x92 { G_UnusedHeader header; @@ -5588,11 +5133,9 @@ struct G_SetChallengeTime_6x95 { G_UnusedHeader header; le_uint32_t client_id = 0; ChallengeTime challenge_time; - // On BB, the token_v4 field is set to (local_client_id + 1) ^ regB (from the - // chl_set_timerecord opcode). This appears to be a basic anti-cheating - // measure. The field is unused on other versions, and even on BB, the client - // doesn't check token_v4 upon receipt, so it's likely just for server-side - // verification. + // On BB, the token_v4 field is set to (local_client_id + 1) ^ regB (from the chl_set_timerecord opcode). This + // appears to be a basic anti-cheating measure. The field is unused on other versions, and even on BB, the client + // doesn't check token_v4 upon receipt, so it's likely just for server-side verification. le_uint32_t token_v4 = 0; le_uint32_t unused = 0; } __packed_ws__(G_SetChallengeTime_6x95, 0x14); @@ -5622,18 +5165,17 @@ struct G_UpdateEntityStat_6x9A { G_EntityIDHeader header; le_uint16_t entity_index = 0; // Values for what: - // 0 = subtract HP - // 1 = subtract TP - // 2 = subtract Meseta - // 3 = add HP - // 4 = add TP + // 0 = subtract HP + // 1 = subtract TP + // 2 = subtract Meseta + // 3 = add HP + // 4 = add TP uint8_t what = 0; uint8_t amount = 0; } __packed_ws__(G_UpdateEntityStat_6x9A, 8); // 6x9B: Level up all techniques (protected on GC NTE/V3/V4) -// Used in battle mode if the rules specify that techniques should level up -// upon character death. +// Used in battle mode if the rules specify that techniques should level up upon character death. struct G_LevelUpAllTechniques_6x9B { G_UnusedHeader header; @@ -5642,19 +5184,16 @@ struct G_LevelUpAllTechniques_6x9B { } __packed_ws__(G_LevelUpAllTechniques_6x9B, 8); // 6x9C: Set enemy low game flags (not valid on Episode 3) -// This command only has an effect in Ultimate mode. It sets the low 6 bits of -// game_flags (those that match 0x0000003F). +// This command only has an effect in Ultimate mode; it sets the low 6 bits of game_flags (those that match 0x3F). struct G_SetEnemyLowGameFlagsUltimate_6x9C { G_EntityIDHeader header; - // A virtual function is called on the enemy if low_game_flags is equal to - // any of 0x02, 0x04, 0x10, or 0x20. + // A virtual function is called on the enemy if low_game_flags is equal to any of 0x02, 0x04, 0x10, or 0x20. le_uint32_t low_game_flags = 0; } __packed_ws__(G_SetEnemyLowGameFlagsUltimate_6x9C, 8); // 6x9D: Set dead flag (Challenge mode; not valid on Episode 3) -// This command causes the specified client to appear in the dead players list -// when the Challenge mode retry menu appears. +// This command makes the specified client appear in the dead players list when the Challenge mode retry menu appears. struct G_SetChallengeDeadFlag_6x9D { G_UnusedHeader header; @@ -5662,9 +5201,8 @@ struct G_SetChallengeDeadFlag_6x9D { } __packed_ws__(G_SetChallengeDeadFlag_6x9D, 8); // 6x9E: Play camera shutter sound -// This subcommand is only used on PSO PC and PC NTE. It is not implemented (and -// therefore ignored) by all prior versions, and all later versions have -// handlers for this command, but the handlers do nothing. +// This subcommand is only used on PSO PC and PC NTE. It is not implemented (and therefore ignored) by all prior +// versions, and all later versions have handlers for this command, but the handlers do nothing. struct G_PlayerCameraShutterSound_6x9E { G_ClientIDHeader header; @@ -5696,12 +5234,10 @@ struct G_RevivePlayer_V3_BB_6xA1 { G_ClientIDHeader header; } __packed_ws__(G_RevivePlayer_V3_BB_6xA1, 4); -// 6xA2: Specializable item drop request (not valid on pre-V3; handled by -// server on BB) +// 6xA2: Specializable item drop request (not valid on pre-V3; handled by server on BB) struct G_SpecializableItemDropRequest_6xA2 : G_StandardDropItemRequest_PC_V3_BB_6x60 { - // These fields directly map to param3-6 in the ObjectSetEntry structure from - // which the box was created. + // These fields directly map to param3-6 in the ObjectSetEntry structure from which the box was created. /* 18 */ le_float param3 = 0.0f; /* 1C */ le_int32_t param4 = 0; /* 20 */ le_int32_t param5 = 0; @@ -5736,16 +5272,16 @@ struct G_OlgaFlowBossActions_6xA4_6xA5 { struct G_ModifyTradeProposal_6xA6 { G_ClientIDHeader header; // Values for what: - // 0 = Propose (result = 0), accept (result = 2), or reject (result = 3) - // TODO: amount is used when what=0; what is it used for? - // 1 = Add item - // 2 = Remove item - // 3 = First confirm (result = 0) or cancel first confirm (result = 4) - // 4 = Second confirm (result = 0) or cancel second confirm (result = 4) - // 5 = Cancel trade proposal (result = 4) - // 6 = Reject proposal (currently trading with another player) - // 7 = ??? (TODO) - // Anything else = command is ignored + // 0 = Propose (result = 0), accept (result = 2), or reject (result = 3) + // TODO: amount is used when what=0; what is it used for? + // 1 = Add item + // 2 = Remove item + // 3 = First confirm (result = 0) or cancel first confirm (result = 4) + // 4 = Second confirm (result = 0) or cancel second confirm (result = 4) + // 5 = Cancel trade proposal (result = 4) + // 6 = Reject proposal (currently trading with another player) + // 7 = ??? (TODO) + // Anything else = command is ignored uint8_t what = 0; uint8_t result = 0; be_uint16_t unknown_a1; // Only used if what = 7 @@ -5792,8 +5328,7 @@ struct G_BarbaRayBossActions_6xAA { } __packed_ws__(G_BarbaRayBossActions_6xAA, 0x0C); // 6xAB: Create lobby chair (not valid on pre-V3) (protected on V3/V4) -// This command appears to be different on GC NTE than on any other version. -// It's not known what it does on GC NTE. +// This command appears to be different on GC NTE than on any other version. It's not known what it does on GC NTE. struct G_Unknown_GCNTE_6xAB { G_EntityIDHeader header; @@ -5805,8 +5340,8 @@ struct G_CreateLobbyChair_6xAB { G_ClientIDHeader header; le_uint16_t unused = 0; // Only two bits in this field have meanings: - // 01 = unknown - // 02 = which pose/animation (on GC, 0 = X+A, 1 = X+B) + // 01 = unknown + // 02 = which pose/animation (on GC, 0 = X+A, 1 = X+B) le_uint16_t flags = 0; } __packed_ws__(G_CreateLobbyChair_6xAB, 8); @@ -5820,10 +5355,9 @@ struct G_Unknown_GCNTE_6xAC { } __packed_ws__(G_Unknown_GCNTE_6xAC, 0x0C); // 6xAC: Delete multiple inventory items (protected on V3/V4) -// This command appears to delete multiple items from a player's inventory. -// It's not clear when or why this would be used; the client never sends it. -// The disassembly also is somewhat confusing, and it's not clear that it even -// works properly. +// This command appears to delete multiple items from a player's inventory. It's not clear when or why this would be +// used; the client never sends it. The disassembly also is somewhat confusing, and it's not clear that it even works +// properly. struct G_DeleteMultipleInventoryItems_6xAC { G_ClientIDHeader header; @@ -5831,8 +5365,7 @@ struct G_DeleteMultipleInventoryItems_6xAC { parray item_ids; } __packed_ws__(G_DeleteMultipleInventoryItems_6xAC, 0x80); -// 6xAD: Olga Flow subordinate boss actions (not valid on pre-V3, Episode 3, or -// GC Trial Edition) +// 6xAD: Olga Flow subordinate boss actions (not valid on pre-V3, Episode 3, or GC Trial Edition) struct G_OlgaFlowSubordinateBossActions_6xAD { G_UnusedHeader header; @@ -5847,23 +5380,20 @@ struct G_SetAnimationState_6xAE { G_ClientIDHeader header; le_uint16_t animation_number = 0; le_uint16_t unused = 0; - // This field contains the flags field on the sender's TObjPlayer object. - // If the bit 04000000 is set in this field, then (flags & 1C000000) is or'ed - // into the TObjPlayer's flags field. All other bits are ignored. + // This field contains the flags field on the sender's TObjPlayer object. If the bit 04000000 is set in this field, + // then (flags & 1C000000) is or'ed into the TObjPlayer's flags field. All other bits are ignored. le_uint32_t flags = 0; le_float animation_timer = 0; } __packed_ws__(G_SetAnimationState_6xAE, 0x10); -// 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected -// on V3/V4) +// 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) struct G_TurnLobbyChair_6xAF { G_ClientIDHeader header; le_uint32_t angle = 0; // In range [0x0000, 0xFFFF] } __packed_ws__(G_TurnLobbyChair_6xAF, 8); -// 6xB0: Move lobby chair (not valid on pre-V3 or GC Trial Edition) (protected -// on V3/V4) +// 6xB0: Move lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) struct G_MoveLobbyChair_6xB0 { G_ClientIDHeader header; @@ -5874,9 +5404,8 @@ struct G_MoveLobbyChair_6xB0 { // This subcommand is completely ignored. // 6xB2: Play sound from player (not valid on pre-V3 or GC Trial Edition) -// This command is sent when a snapshot is taken on PSO GC, but it can be used -// to play any sound. The sound is centered on the local player (even if the -// local player does not match client_id), or if client_id is FFFF, it is not +// This command is sent when a snapshot is taken on PSO GC, but it can be used to play any sound. The sound is centered +// on the local player (even if the local player does not match client_id), or if client_id is FFFF, it is not // localized at all and it just played globally. struct G_PlaySoundFromPlayer_6xB2 { @@ -5897,9 +5426,8 @@ struct G_Unknown_XB_6xB3 { // 6xB3: CARD battle server data request (Episode 3) -// CARD battle subcommands have multiple subsubcommands, which we name 6xBYxZZ, -// where Y = 3, 4, 5, or 6, and ZZ is any byte. The formats of these -// subsubcommands are described at the end of this file. +// CARD battle subcommands have multiple subsubcommands, which we name 6xBYxZZ, where Y = 3, 4, 5, or 6, and ZZ is any +// byte. The formats of these subsubcommands are described at the end of this file. // The common format for CARD battle subcommand headers is: struct G_CardBattleCommandHeader { @@ -5908,26 +5436,21 @@ struct G_CardBattleCommandHeader { le_uint16_t unused1 = 0x0000; uint8_t subsubcommand = 0x00; // See 6xBx subcommand table (after this table) uint8_t sender_client_id = 0x00; - // If mask_key is nonzero, the remainder of the data (after unused2 in this - // struct) is encrypted using a simple algorithm, which is implemented in - // set_mask_for_ep3_game_command in SendCommands.cc. The Episode 3 client - // never sends commands that have a nonzero value in this field, but it does - // properly handle received commands with nonzero values in this field. - // This only applies to Episode 3 final - the Trial Edition does not support - // masking and may send uninitialized data in this field. + // If mask_key is nonzero, the remainder of the data (after unused2 in this struct) is encrypted using a simple + // algorithm, which is implemented in set_mask_for_ep3_game_command in SendCommands.cc. The Episode 3 client never + // sends commands that have a nonzero value in this field, but it does properly handle received commands with nonzero + // values in this field. This only applies to Episode 3 final - the Trial Edition does not support masking and may + // send uninitialized data in this field. uint8_t mask_key = 0x00; uint8_t unused2 = 0x00; } __packed_ws__(G_CardBattleCommandHeader, 8); -// Unlike all other 6x subcommands, the 6xB3 subcommand is sent to the server in -// a CA command instead of a 6x, C9, or CB command. (For this reason, we -// generally refer to 6xB3xZZ commands as CAxZZ commands instead.) The server is -// expected to reply to CA commands with one or more 6xB4 subcommands instead of -// forwarding them. The logic for doing so is implemented in Episode3/Server.cc -// and the surrounding classes. +// Unlike all other 6x subcommands, the 6xB3 subcommand is sent to the server in a CA command instead of a 6x, C9, or +// CB command. (For this reason, we generally refer to 6xB3xZZ commands as CAxZZ commands instead.) The server is +// expected to reply to CA commands with one or more 6xB4 subcommands instead of forwarding them. The logic for doing +// so is implemented in Episode3/Server.cc and the surrounding classes. -// The 6xB3 subcommand has a longer header than 6xB4 and 6xB5. This header is -// common to all 6xB3x (CAx) subcommands. +// The 6xB3 subcommand has a longer header than 6xB4 and 6xB5. This header is common to all 6xB3x (CAx) subcommands. struct G_CardServerDataCommandHeader { /* 00 */ uint8_t subcommand = 0xB3; /* 01 */ uint8_t size = 0x00; @@ -5936,10 +5459,9 @@ struct G_CardServerDataCommandHeader { /* 05 */ uint8_t sender_client_id = 0x00; /* 06 */ uint8_t mask_key = 0x00; // Same meaning as in G_CardBattleCommandHeader /* 07 */ uint8_t unused2 = 0x00; - // The sequence number space is split into 30-bit subspaces by the last 2 - // bits, which are the sender's client ID. That is, player 0 will send - // sequence number 0, then 4, then 8, etc; player 1 will send 1, then 5, then - // 9, etc. and so on for players 2 and 3. + // The sequence number space is split into 30-bit subspaces by the last 2 bits, which are the sender's client ID. + // That is, player 0 will send sequence number 0, then 4, then 8, etc; player 1 will send 1, then 5, then 9, etc. and + // so on for players 2 and 3. /* 08 */ be_uint32_t sequence_num = 0; /* 0C */ be_uint32_t context_token = 0; /* 10 */ @@ -5963,12 +5485,10 @@ struct G_ShopContentsRequest_BB_6xB5 { } __packed_ws__(G_ShopContentsRequest_BB_6xB5, 8); // 6xB6: Episode 3 map list and map contents (server->client only) -// Unlike 6xB3-6xB5, these commands cannot be masked. Also unlike 6xB3-6xB5, -// there are only two subsubcommands, so we list them inline here. -// These subcommands can be rather large, so they should be sent with the 6C -// command instead of the 60 command. (The difference in header format, -// including the extended size field, is likely the reason for 6xB6 being a -// separate subcommand from the other CARD battle subcommands.) +// Unlike 6xB3-6xB5, these commands cannot be masked. Also unlike 6xB3-6xB5, there are only two subsubcommands, so we +// list them inline here. These subcommands can be rather large, so they should be sent with the 6C command instead of +// the 60 command. (The difference in header format, including the extended size field, is likely the reason for 6xB6 +// being a separate subcommand from the other CARD battle subcommands.) struct G_MapSubsubcommand_Ep3_6xB6 { G_ExtendedHeaderT header; @@ -5980,9 +5500,8 @@ struct G_MapList_Ep3_6xB6x40 { G_MapSubsubcommand_Ep3_6xB6 header; le_uint16_t compressed_data_size = 0; le_uint16_t unused = 0; - // PRS-compressed map list data follows here. newserv generates this from the - // map index when requested; see the MapList struct in Episode3/DataIndexes.hh - // and Episode3::MapIndex::get_compressed_map_list for details on the format. + // PRS-compressed map list data follows here. newserv generates this from the map index when requested; see the + // MapList struct in Episode3/DataIndexes.hh and Episode3::MapIndex::get_compressed_map_list for format details. } __packed_ws__(G_MapList_Ep3_6xB6x40, 0x10); struct G_MapData_Ep3_6xB6x41 { @@ -5990,13 +5509,11 @@ struct G_MapData_Ep3_6xB6x41 { le_uint32_t map_number = 0; le_uint16_t compressed_data_size = 0; le_uint16_t unused = 0; - // PRS-compressed map data follows here (which decompresses to an - // Episode3::MapDefinition). + // PRS-compressed map data follows here (which decompresses to an Episode3::MapDefinition). } __packed_ws__(G_MapData_Ep3_6xB6x41, 0x14); // 6xB6: BB shop contents (server->client only) -// The client will ignore this command (leaving the player softlocked) if there -// are too many items. The limits are: +// The client will ignore this command (leaving the player softlocked) if there are too many items. The limits are: // - Tool shop: up to 18 items allowed // - Weapon shop: up to 16 items allowed // - Armor shop: up to 21 items allowed @@ -6011,8 +5528,8 @@ struct G_ShopContents_BB_6xB6 { } __packed_ws__(G_ShopContents_BB_6xB6, 0x198); // 6xB7: Alias for 6xB3 (Episode 3 Trial Edition) -// This command behaves exactly the same as 6xB3. This alias exists only in -// Episode 3 Trial Edition; it was removed in the final release. +// This command behaves exactly the same as 6xB3. This alias exists only in Episode 3 Trial Edition; it was removed in +// the final release. // 6xB7: BB buy shop item (handled by the server) @@ -6026,8 +5543,8 @@ struct G_BuyShopItem_BB_6xB7 { } __packed_ws__(G_BuyShopItem_BB_6xB7, 0x0C); // 6xB8: Alias for 6xB4 (Episode 3 Trial Edition) -// This command behaves exactly the same as 6xB4. This alias exists only in -// Episode 3 Trial Edition; it was removed in the final release. +// This command behaves exactly the same as 6xB4. This alias exists only in Episode 3 Trial Edition; it was removed in +// the final release. // 6xB8: BB identify item request (via tekker) (handled by the server) @@ -6037,8 +5554,8 @@ struct G_IdentifyItemRequest_6xB8 { } __packed_ws__(G_IdentifyItemRequest_6xB8, 8); // 6xB9: Alias for 6xB5 (Episode 3 Trial Edition) -// This command behaves exactly the same as 6xB5. This alias exists only in -// Episode 3 Trial Edition; it was removed in the final release. +// This command behaves exactly the same as 6xB5. This alias exists only in Episode 3 Trial Edition; it was removed in +// the final release. // 6xB9: BB provisional tekker result @@ -6048,21 +5565,20 @@ struct G_IdentifyResult_BB_6xB9 { } __packed_ws__(G_IdentifyResult_BB_6xB9, 0x18); // 6xBA: Sync card trade state (Episode 3) -// This command calls various member functions in TCardTrade. This is used -// after both players are standing at the respective kiosks and are ready to -// trade cards. +// This command calls various member functions in TCardTrade. This is used after both players are standing at the +// respective kiosks and are ready to trade cards. struct G_SyncCardTradeState_Ep3_6xBA { G_ClientIDHeader header; // Values for what: - // 1 = add card to trade (card_id and count used) - // 2 = remove card from trade (card_id and count used) - // 3 = first confirmation - // 4 = cancel first confirmation - // 5 = second confirmation - // 6 = cancel second confirmation - // 7 = leave trade window - // Anything else = does nothing + // 1 = add card to trade (card_id and count used) + // 2 = remove card from trade (card_id and count used) + // 3 = first confirmation + // 4 = cancel first confirmation + // 5 = second confirmation + // 6 = cancel second confirmation + // 7 = leave trade window + // Anything else = does nothing le_uint16_t what = 0; le_uint16_t unused = 0; le_uint32_t card_id = 0; // Only used when what = 1 or 2 @@ -6077,20 +5593,18 @@ struct G_AcceptItemIdentification_BB_6xBA { } __packed_ws__(G_AcceptItemIdentification_BB_6xBA, 8); // 6xBB: Sync card trade server state (Episode 3) -// This command calls various member functions in TCardTradeServer. This is -// used before both players have entered the card trade sequence (as opposed to -// 6xBA, which is used during that sequence). +// This command calls various member functions in TCardTradeServer. This is used before both players have entered the +// card trade sequence (as opposed to 6xBA, which is used during that sequence). struct G_SyncCardTradeServerState_Ep3_6xBB { G_ClientIDHeader header; // Values for what: - // 0 = request slot (leader sends accept message with what=1) - // 1 = accept slot (args[0] is the accepted client ID) - // 2 = cancel all slot requests - // 3 = replace all slots (args[0, 1] are the two client IDs to accept into - // the two slots) - // 4 = relinquish all slots - // Anything else = does nothing + // 0 = request slot (leader sends accept message with what=1) + // 1 = accept slot (args[0] is the accepted client ID) + // 2 = cancel all slot requests + // 3 = replace all slots (args[0, 1] are the two client IDs to accept into the two slots) + // 4 = relinquish all slots + // Anything else = does nothing le_uint16_t what = 0; le_uint16_t slot = 0; // Must be 0 or 1 (not bounds checked!) parray args; @@ -6104,12 +5618,10 @@ struct G_RequestBankContents_BB_6xBB { } __packed_ws__(G_RequestBankContents_BB_6xBB, 0x08); // 6xBC: Card counts (Episode 3) -// This is sent by the client in response to a 6xB5x38 command. This is used -// along with 6xB5x38 so clients can see each other's card counts. Curiously, -// this command is smaller than 0x400 bytes (even on NTE) but uses the extended -// subcommand format anyway. -// An Episode 3 client will crash if it receives this command when the card -// trade window is not active. +// This is sent by the client in response to a 6xB5x38 command. This is used along with 6xB5x38 so clients can see each +// other's card counts. Curiously, this command is smaller than 0x400 bytes (even on NTE) but uses the extended +// subcommand format anyway. An Episode 3 client will crash if it receives this command when the card trade window is +// not active. struct G_CardCounts_Ep3NTE_6xBC { G_ExtendedHeaderT header; @@ -6124,27 +5636,24 @@ struct G_CardCounts_Ep3_6xBC { } __packed_ws__(G_CardCounts_Ep3_6xBC, 0x2FC); // 6xBC: BB bank contents (server->client only) -// This is sent in response to a 6xBB command. If the checksum in this command -// doesn't match the checksum the client sent in its 6xBB command, the client -// overwrites its bank data with the data sent in this command. +// This is sent in response to a 6xBB command. If the checksum in this command doesn't match the checksum the client +// sent in its 6xBB command, the client overwrites its bank data with the data sent in this command. struct G_BankContentsHeader_BB_6xBC { G_ExtendedHeaderT header; - le_uint32_t checksum = 0; // can be random; client won't notice + le_uint32_t checksum = 0; // Can be random; client won't notice le_uint32_t num_items = 0; le_uint32_t meseta = 0; - // Item data follows + // Item data follows here } __packed_ws__(G_BankContentsHeader_BB_6xBC, 0x14); // 6xBD: Word select during battle (Episode 3; not Trial Edition) -// Note: This structure does not have a normal header - the client ID field is -// big-endian! +// Note: This structure does not have a normal header - the client ID field is big-endian! struct G_PrivateWordSelect_Ep3_6xBD { G_ClientIDHeader header; WordSelectMessage message; - // This field has the same meaning as the first byte in an 06 command's - // message when sent during an Episode 3 battle. + // This field has the same meaning as the first byte in an 06 command's message when sent during an Episode 3 battle. uint8_t private_flags = 0; parray unused; } __packed_ws__(G_PrivateWordSelect_Ep3_6xBD, 0x24); @@ -6185,10 +5694,9 @@ struct G_ChangeLobbyMusic_Ep3_6xBF { } __packed_ws__(G_ChangeLobbyMusic_Ep3_6xBF, 8); // 6xBF: Give EXP (BB) (server->client only) -// newserv implements an extension that causes this command to show the purple -// EXP numbers which are normally generated by the client instead. This -// requires the server to also send the enemy ID that generated the EXP, hence -// the extension struct here. See ServerEXPDisplay.59NL.patch.s for details. +// newserv implements an extension that causes this command to show the purple EXP numbers which are normally generated +// by the client instead. This requires the server to also send the enemy ID that generated the EXP, hence the +// extension struct here. See ServerEXPDisplay.59NL.patch.s for details. struct G_GiveExperience_BB_6xBF { G_ClientIDHeader header; @@ -6221,8 +5729,8 @@ struct G_TeamInvitationAction_BB_6xC1_6xC2_6xCD_6xCE { } __packed_ws__(G_TeamInvitationAction_BB_6xC1_6xC2_6xCD_6xCE, 0x60); // 6xC3: Split stacked item (BB; handled by the server) -// Note: This is not sent if an entire stack is dropped; in that case, a normal -// item drop subcommand is generated instead. +// Note: This is not sent if an entire stack is dropped; in that case, a normal item drop subcommand is generated +// instead. struct G_SplitStackedItem_BB_6xC3 { G_ClientIDHeader header; @@ -6258,8 +5766,8 @@ struct G_StealEXP_BB_6xC6 { struct G_ChargeAttack_BB_6xC7 { G_ClientIDHeader header; - // Tethealla (at least, the ancient public version of it) treats this as - // signed, and gives the player money in that case. We don't do so. + // Tethealla (at least, the ancient public version of it) treats this as signed, and gives the player money in that + // case. We don't do so. le_uint32_t meseta_amount = 0; } __packed_ws__(G_ChargeAttack_BB_6xC7, 8); @@ -6281,8 +5789,8 @@ struct G_AdjustPlayerMeseta_BB_6xC9 { } __packed_ws__(G_AdjustPlayerMeseta_BB_6xC9, 8); // 6xCA: Request item reward from quest (BB; handled by server) -// The server should create the item in the player's inventory using 6xBE if it -// matches at least one of the item creation masks in the quest's header. +// The server should create the item in the player's inventory using 6xBE if it matches at least one of the item +// creation masks in the quest's header. struct G_QuestCreateItem_BB_6xCA { G_UnusedHeader header; @@ -6308,7 +5816,7 @@ struct G_ExchangeItemForTeamPoints_BB_6xCC { // 6xCD: Transfer master (BB) // 6xCE: Accept master transfer (BB) -// Same format as 6xC1 +// Same format as 6xC1. // 6xCF: Start battle (BB) @@ -6318,8 +5826,7 @@ struct G_StartBattle_BB_6xCF { } __packed_ws__(G_StartBattle_BB_6xCF, 0x34); // 6xD0: Battle mode level up (BB; handled by server) -// Requests the client to be leveled up by num_levels levels. The server should -// respond with a 6x30 command. +// Requests the client to be leveled up by num_levels levels. The server should respond with a 6x30 command. struct G_BattleModeLevelUp_BB_6xD0 { G_ClientIDHeader header; @@ -6360,18 +5867,15 @@ struct G_SetQuestCounter_BB_6xD2 { struct G_Unknown_BB_6xD4 { G_UnusedHeader header; le_uint16_t action = 0; // Must be in [0, 5] - // object_identifier must be in [0, 15]; it should match param4 of the - // relevant object. + // object_identifier must be in [0, 15]; it should match param4 of the relevant object. uint8_t object_identifier = 0; uint8_t unused = 0; } __packed_ws__(G_Unknown_BB_6xD4, 8); // 6xD5: Exchange item in quest (BB; handled by server) -// The client sends this when it executes an F953 quest opcode. -// If any item matching find_item.data1[0-2] is present in the player's -// inventory, the server should destroy that item using 6x29, then create -// replace_item in the player's inventory using 6xBE if it matches at least one -// of the item creation masks in the quest's header. +// The client sends this when it executes an F953 quest opcode. If any item matching find_item.data1[0-2] is present in +// the player's inventory, the server should destroy that item using 6x29, then create replace_item in the player's +// inventory using 6xBE if it matches at least one of the item creation masks in the quest's header. struct G_QuestExchangeItem_BB_6xD5 { G_ClientIDHeader header; @@ -6391,9 +5895,8 @@ struct G_WrapItem_BB_6xD6 { } __packed_ws__(G_WrapItem_BB_6xD6, 0x1C); // 6xD7: Paganini Photon Drop exchange (BB; handled by server) -// The client sends this when it executes an F955 quest opcode. -// The server should create the item in the player's inventory using 6xBE if it -// matches at least one of the item creation masks in the quest's header. +// The client sends this when it executes an F955 quest opcode. The server should create the item in the player's +// inventory using 6xBE if it matches at least one of the item creation masks in the quest's header. struct G_PaganiniPhotonDropExchange_BB_6xD7 { G_ClientIDHeader header; @@ -6415,13 +5918,11 @@ struct G_AddSRankWeaponSpecial_BB_6xD8 { } __packed_ws__(G_AddSRankWeaponSpecial_BB_6xD8, 0x24); // 6xD9: Momoka item exchange (BB; handled by server) -// The client sends this when it executes an F95B quest opcode. The client has -// an unfortunate bug where it doesn't set the size field when generating this -// command, so the size ends up as an uninitialized value and the client sends -// more (or less!) data than necessary. The MomokaItemExchangeFix patch fixes -// this bug. -// The server should create the item in the player's inventory using 6xBE if it -// matches at least one of the item creation masks in the quest's header. +// The client sends this when it executes an F95B quest opcode. The client has an unfortunate bug where it doesn't set +// the size field when generating this command, so the size ends up as an uninitialized value and the client sends more +// (or less!) data than necessary. The MomokaItemExchangeFix patch fixes this bug. +// The server should create the item in the player's inventory using 6xBE if it matches at least one of the item +// creation masks in the quest's header. struct G_MomokaItemExchange_BB_6xD9 { /* 00 */ G_ClientIDHeader header; @@ -6454,9 +5955,8 @@ struct G_UpgradeWeaponAttribute_BB_6xDA { struct G_ExchangeItemInQuest_BB_6xDB { G_ClientIDHeader header; - // If this is 0, the command is identical to 6x29. If this is 1, a function - // similar to find_item_by_id is called instead of find_item_by_id, but I - // don't yet know what exactly the logic differences are (TODO). + // If this is 0, the command is identical to 6x29. If this is 1, a function similar to find_item_by_id is called + // instead of find_item_by_id, but I don't yet know what exactly the logic differences are (TODO). le_uint32_t unknown_a1 = 0; le_uint32_t item_id = 0; le_uint32_t amount = 0; @@ -6471,20 +5971,18 @@ struct G_Episode4BossActions_BB_6xDC { } __packed_ws__(G_Episode4BossActions_BB_6xDC, 8); // 6xDD: Set EXP multiplier (BB) -// header.param specifies the EXP multiplier. It is 1-based, so the value 2 -// means all EXP is doubled, for example. This only affects what the client -// shows when an enemy is killed; actual EXP gains are controlled by the server -// in response to the 6xC8 command. +// header.param specifies the EXP multiplier. It is 1-based, so the value 2 means all EXP is doubled, for example. This +// only affects what the client shows when an enemy is killed; actual EXP gains are controlled by the server in +// response to the 6xC8 command. struct G_SetEXPMultiplier_BB_6xDD { G_ParameterHeader header; } __packed_ws__(G_SetEXPMultiplier_BB_6xDD, 4); // 6xDE: Exchange Secret Lottery Ticket (BB; handled by server) -// The client sends this when it executes an F95C quest opcode. -// There is a bug in the client here: it sets the subcommand size to 2 instead -// of 3, so the last relevant field (failure_label) is not sent to the server. -// This is fixed in the MomokaItemExchangeFix patch. +// The client sends this when it executes an F95C quest opcode. There is a bug in the client here: it sets the +// subcommand size to 2 instead of 3, so the last relevant field (failure_label) is not sent to the server. This is +// fixed in the MomokaItemExchangeFix patch. struct G_ExchangeSecretLotteryTicket_Incomplete_BB_6xDE { G_ClientIDHeader header; @@ -6518,8 +6016,8 @@ struct G_RequestItemDropFromQuest_BB_6xE0 { } __packed_ws__(G_RequestItemDropFromQuest_BB_6xE0, 0x10); // 6xE1: Exchange Photon Tickets (BB; handled by server) -// The client sends this when it executes an F95F quest opcode. The comments -// denote where in the command each argument to that opcode is sent. +// The client sends this when it executes an F95F quest opcode. The comments denote where in the command each argument +// to that opcode is sent. struct G_ExchangePhotonTickets_BB_6xE1 { G_ClientIDHeader header; @@ -6544,8 +6042,7 @@ struct G_GetMesetaSlotPrize_BB_6xE2 { } __packed_ws__(G_GetMesetaSlotPrize_BB_6xE2, 0x10); // 6xE3: Set Meseta slot prize result (BB) -// The client only uses this to populate the quest text -// replacement token. +// The client only uses this to populate the quest text replacement token. struct G_SetMesetaSlotPrizeResult_BB_6xE3 { G_ClientIDHeader header; @@ -6581,25 +6078,22 @@ struct G_SetMesetaSlotPrizeResult_BB_6xE3 { // 6xFE: Invalid subcommand // 6xFF: Invalid subcommand -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// EPISODE 3 CARD BATTLE SUBSUBCOMMANDS //////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EPISODE 3 CARD BATTLE SUBSUBCOMMANDS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// The Episode 3 CARD battle subsubcommands are used in commands 6xB3, 6xB4, and -// 6xB5. Note that even though there's no overlap in the subsubcommand number -// space, the various subsubcommands must be used with the correct 6xBx -// subcommand - the client will ignore the command if sent via the wrong 6xBx -// subcommand. (For example, sending a 6xB5x02 command will do nothing because -// subsubcommand 02 is only valid for the 6xB4 subcommand.) This table is known -// to be complete, so invalid commands are not listed. +// The Episode 3 CARD battle subsubcommands are used in commands 6xB3, 6xB4, and 6xB5. Note that even though there's no +// overlap in the subsubcommand number space, the various subsubcommands must be used with the correct 6xBx subcommand; +// the client will ignore the command if sent via the wrong 6xBx subcommand. (For example, sending a 6xB5x02 command +// will do nothing because subsubcommand 02 is only valid for the 6xB4 subcommand.) This table is known to be complete, +// so invalid commands are not listed. -// In general, 6xB3 (CAx) commands are sent by the client when it wants to take -// an action that affects game state held on the server side. The server will -// send one or more 6xB4 commands in response to update all clients' views of -// the game state. 6xB5 commands do not affect state held on the server side, -// and are generally only of concern on the client side. +// In general, 6xB3 (CAx) commands are sent by the client when it wants to take an action that affects game state held +// on the server side. The server will send one or more 6xB4 commands in response to update all clients' views of the +// game state. 6xB5 commands do not affect state held on the server side, and are generally only of concern on the +// client side. // 6xB4x02: Update hand and equips @@ -6626,10 +6120,10 @@ struct G_UpdateShortStatuses_Ep3_6xB4x04 { /* 0008 */ le_uint16_t client_id = 0; /* 000A */ le_uint16_t unused = 0; // The slots in this array have heterogeneous meanings. Specifically: - // [0] is the SC card status - // [1] through [6] are hand cards - // [7] through [14] are set FC cards (items/creatures) - // [15] is the set assist card + // [0] is the SC card status + // [1] through [6] are hand cards + // [7] through [14] are set FC cards (items/creatures) + // [15] is the set assist card /* 000C */ parray card_statuses; /* 010C */ } __packed_ws__(G_UpdateShortStatuses_Ep3_6xB4x04, 0x10C); @@ -6680,15 +6174,14 @@ struct G_SetActionState_Ep3_6xB4x09 { } __packed_ws__(G_SetActionState_Ep3_6xB4x09, 0x70); // 6xB4x0A: Update action chain and metadata -// This command is used by Trial Edition. The final version sends 6xB4x4C, -// 6xB4x4D, and 6xB4x4E instead, but still has a handler for this command. +// This command is used by Trial Edition. The final version sends 6xB4x4C, 6xB4x4D, and 6xB4x4E instead, but still has +// a handler for this command. struct G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A { /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A) / 4, 0, 0x0A, 0, 0, 0}; /* 0008 */ le_uint16_t client_id = 0; - // set_index must be 0xFF, or be in the range [0, 9]. If it's 0xFF, all nine - // chains and metadatas are cleared for the client; otherwise, the provided - // chain and metadata are copied into the slot specified by set_index. + // set_index must be 0xFF, or be in the range [0, 9]. If it's 0xFF, all nine chains and metadatas are cleared for the + // client; otherwise, the provided chain and metadata are copied into the slot specified by set_index. /* 000A */ int8_t index = 0; /* 000B */ uint8_t unused = 0; /* 000C */ Episode3::ActionChainWithCondsTrial chain; @@ -6726,9 +6219,8 @@ struct G_EndInitialRedrawPhase_Ep3_CAx0C { // 6xB3x0D / CAx0D: End non-action phase // Internal names: EndPhaseDice, EndPhaseSet, EndPhaseMove, EndPhaseDraw -// This command is sent when the client has no more actions to take during the -// current phase. This command isn't used for ending the attack or defense -// phases; for those phases, CAx12 and CAx28 are used instead. +// This command is sent when the client has no more actions to take during the current phase. This command isn't used +// for ending the attack or defense phases; for those phases, CAx12 and CAx28 are used instead. struct G_EndNonAttackPhase_Ep3_CAx0D { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndNonAttackPhase_Ep3_CAx0D) / 4, 0, 0x0D, 0, 0, 0, 0, 0}; @@ -6771,10 +6263,9 @@ struct G_MoveFieldCharacter_Ep3_CAx10 { // 6xB3x11 / CAx11: Enqueue action (play card(s) during action phase) // Internal name: ExecActionReq -// This command is used for playing both attacks (and the associated action -// cards), and for playing defense cards. In the attack case, this command is -// sent once for each attack (even if it includes multiple cards); in the -// defense case, this command is sent once for each defense card. +// This command is used for playing both attacks (and the associated action cards), and for playing defense cards. In +// the attack case, this command is sent once for each attack (even if it includes multiple cards); in the defense +// case, this command is sent once for each defense card. struct G_EnqueueAttackOrDefense_Ep3_CAx11 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EnqueueAttackOrDefense_Ep3_CAx11) / 4, 0, 0x11, 0, 0, 0, 0, 0}; @@ -6785,8 +6276,8 @@ struct G_EnqueueAttackOrDefense_Ep3_CAx11 { // 6xB3x12 / CAx12: End attack list (done playing cards during action phase) // Internal name: ExecActionCalc -// This command informs the server that the client is done playing attacks in -// the current round. (In the defense phase, CAx28 is used instead.) +// This command informs the server that the client is done playing attacks in the current round. (In the defense phase, +// CAx28 is used instead.) struct G_EndAttackList_Ep3_CAx12 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndAttackList_Ep3_CAx12) / 4, 0, 0x12, 0, 0, 0, 0, 0}; @@ -6836,11 +6327,9 @@ struct G_Unknown_Ep3_6xB5x17 { } __packed_ws__(G_Unknown_Ep3_6xB5x17, 8); // 6xB5x1A: Force disconnect -// This command seems to cause the client to unconditionally disconnect. The -// player is returned to the main menu (the "The line was disconnected" message -// box is skipped). Unlike all other known ways to disconnect, the client does -// not save when it receives this command, and instead returns directly to the -// main menu. +// This command seems to cause the client to unconditionally disconnect. The player is returned to the main menu (the +// "The line was disconnected" message box is skipped). Unlike all other known ways to disconnect, the client does not +// save when it receives this command, and instead returns directly to the main menu. struct G_ForceDisconnect_Ep3_6xB5x1A { G_CardBattleCommandHeader header = {0xB5, sizeof(G_ForceDisconnect_Ep3_6xB5x1A) / 4, 0, 0x1A, 0, 0, 0}; @@ -6848,9 +6337,8 @@ struct G_ForceDisconnect_Ep3_6xB5x1A { } __packed_ws__(G_ForceDisconnect_Ep3_6xB5x1A, 8); // 6xB3x1B / CAx1B: Set player name -// This command is normally sent during battle setup to populate player slots, -// but is also sent if a player disconnects during battle to switch their -// player type from human to CPU. +// This command is normally sent during battle setup to populate player slots, but is also sent if a player disconnects +// during battle to switch their player type from human to CPU. struct G_SetPlayerName_Ep3_CAx1B { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerName_Ep3_CAx1B) / 4, 0, 0x1B, 0, 0, 0, 0, 0}; @@ -6865,9 +6353,8 @@ struct G_SetPlayerNames_Ep3_6xB4x1C { } __packed_ws__(G_SetPlayerNames_Ep3_6xB4x1C, 0x58); // 6xB3x1D / CAx1D: Request for battle start -// The battle actually begins when the server sends a state flags update (in -// response to this command) that includes RegistrationPhase::BATTLE_STARTED and -// a SetupPhase value other than REGISTRATION. +// The battle actually begins when the server sends a state flags update (in response to this command) that includes +// RegistrationPhase::BATTLE_STARTED and a SetupPhase value other than REGISTRATION. struct G_StartBattle_Ep3_CAx1D { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_StartBattle_Ep3_CAx1D) / 4, 0, 0x1D, 0, 0, 0, 0, 0}; @@ -6885,13 +6372,13 @@ struct G_ActionResult_Ep3_6xB4x1E { } __packed_ws__(G_ActionResult_Ep3_6xB4x1E, 0x10); // 6xB4x1F: Set context token -// This token is sent back in the context_token field of all CA commands from -// the client. It seems Sega never used this functionality. +// This token is sent back in the context_token field of all CA commands from the client. It seems Sega never used this +// functionality. struct G_SetContextToken_Ep3_6xB4x1F { G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetContextToken_Ep3_6xB4x1F) / 4, 0, 0x1F, 0, 0, 0}; - // Note that this field is little-endian, but the corresponding context_token - // field in G_CardServerDataCommandHeader is big-endian! + // Note that this field is little-endian, but the corresponding context_token field in G_CardServerDataCommandHeader + // is big-endian! le_uint32_t context_token = 0; } __packed_ws__(G_SetContextToken_Ep3_6xB4x1F, 0x0C); @@ -6914,9 +6401,8 @@ struct G_EndBattle_Ep3_CAx21 { } __packed_ws__(G_EndBattle_Ep3_CAx21, 0x14); // 6xB4x22: Unknown -// This command appears to be completely unused. The client's handler for this -// command sets a variable on some data structure if it exists, but that data -// structure is never allocated. +// This command appears to be completely unused. The client's handler for this command sets a variable on some data +// structure if it exists, but that data structure is never allocated. struct G_Unknown_Ep3_6xB4x22 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x22) / 4, 0, 0x22, 0, 0, 0}; @@ -6924,8 +6410,7 @@ struct G_Unknown_Ep3_6xB4x22 { } __packed_ws__(G_Unknown_Ep3_6xB4x22, 8); // 6xB4x23: Unknown -// This command was actually sent by Sega's original servers, but it does -// nothing on the client. +// This command was actually sent by Sega's original servers, but it does nothing on the client. struct G_Unknown_Ep3_6xB4x23 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x23) / 4, 0, 0x23, 0, 0, 0}; @@ -6946,9 +6431,8 @@ struct G_Unknown_Ep3_6xB5x27 { } __packed_ws__(G_Unknown_Ep3_6xB5x27, 0x18); // 6xB3x28 / CAx28: End defense list -// This command informs the server that the client is done playing defense -// cards. (In the attack phase, CAx12 is used instead.) attack_number matches -// the attack_number sent in the previous 6xB4x29 command. +// This command informs the server that the client is done playing defense cards. (In the attack phase, CAx12 is used +// instead.) attack_number matches the attack_number sent in the previous 6xB4x29 command. struct G_EndDefenseList_Ep3_CAx28 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndDefenseList_Ep3_CAx28) / 4, 0, 0x28, 0, 0, 0, 0, 0}; @@ -6958,9 +6442,8 @@ struct G_EndDefenseList_Ep3_CAx28 { } __packed_ws__(G_EndDefenseList_Ep3_CAx28, 0x14); // 6xB4x29: Update attack targets -// This is sent when the server begins computing the results of an attack. It -// updates the targets used in the attack (e.g. if targeted items were -// destroyed by a previous attack). +// This is sent when the server begins computing the results of an attack. It updates the targets used in the attack +// (e.g. if targeted items were destroyed by a previous attack). struct G_UpdateAttackTargets_Ep3_6xB4x29 { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAttackTargets_Ep3_6xB4x29) / 4, 0, 0x29, 0, 0, 0}; @@ -6971,9 +6454,8 @@ struct G_UpdateAttackTargets_Ep3_6xB4x29 { } __packed_ws__(G_UpdateAttackTargets_Ep3_6xB4x29, 0x70); // 6xB4x2A: Unknown -// This appears to be unused, even on NTE. It writes an entry into an array of -// four 6-byte structures (doing nothing if there are already 4 present), but -// nothing reads from this array. +// This appears to be unused, even on NTE. It writes an entry into an array of four 6-byte structures (doing nothing if +// there are already 4 present), but nothing reads from this array. struct G_Unknown_Ep3_6xB4x2A { G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x2A) / 4, 0, 0x2A, 0, 0, 0}; @@ -6983,8 +6465,7 @@ struct G_Unknown_Ep3_6xB4x2A { } __packed_ws__(G_Unknown_Ep3_6xB4x2A, 0x10); // 6xB3x2B / CAx2B: Legacy set card -// It seems Sega's servers completely ignored this command. The command name is -// based on a debug message found nearby. +// It seems Sega's servers completely ignored this command. The command name is based on a debug message found nearby. struct G_ExecLegacyCard_Ep3_CAx2B { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_ExecLegacyCard_Ep3_CAx2B) / 4, 0, 0x2B, 0, 0, 0, 0, 0}; @@ -6993,9 +6474,8 @@ struct G_ExecLegacyCard_Ep3_CAx2B { } __packed_ws__(G_ExecLegacyCard_Ep3_CAx2B, 0x14); // 6xB4x2C: Enqueue animation -// This is used for playing the trap and teleport animations (with change_type -// = 1). It's also used for playing the discard entire hand animation (with -// change_type = 3). +// This is used for playing the trap and teleport animations (with change_type = 1). It's also used for playing the +// discard entire hand animation (with change_type = 3). struct G_EnqueueAnimation_Ep3_6xB4x2C { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_EnqueueAnimation_Ep3_6xB4x2C) / 4, 0, 0x2C, 0, 0, 0}; @@ -7012,12 +6492,10 @@ struct G_EnqueueAnimation_Ep3_6xB4x2C { struct G_RecreateMultiplePlayers_Ep3_6xB5x2D { G_CardBattleCommandHeader header = {0xB5, sizeof(G_RecreateMultiplePlayers_Ep3_6xB5x2D) / 4, 0, 0x2D, 0, 0, 0}; - // This array is indexed by client ID. When a client receives this command - // and its corresponding entry in this array is not zero, it sends a 6x70 - // command to itself containing its own player data. It's not clear what the - // function of this is intended to be. - // TODO: Figure out if tournament fast loading can be implemented using this - // to fix the stuck-in-wall glitch. + // This array is indexed by client ID. When a client receives this command and its corresponding entry in this array + // is not zero, it sends a 6x70 command to itself containing its own player data. It's not clear what the function of + // this is intended to be. + // TODO: Figure out if tournament fast loading can be implemented using this to fix the stuck-in-wall glitch. parray client_ids_to_recreate; } __packed_ws__(G_RecreateMultiplePlayers_Ep3_6xB5x2D, 0x0C); @@ -7026,9 +6504,9 @@ struct G_RecreateMultiplePlayers_Ep3_6xB5x2D { struct G_BattleEndNotification_Ep3_6xB5x2E { G_CardBattleCommandHeader header = {0xB5, sizeof(G_BattleEndNotification_Ep3_6xB5x2E) / 4, 0, 0x2E, 0, 0, 0}; // Values for end_type: - // 0 = Battle results screen - // 1 = Go directly to Morgue? (TODO: test this) - // Anything else = command is ignored + // 0 = Battle results screen + // 1 = Go directly to Morgue? (TODO: test this) + // Anything else = command is ignored uint8_t end_type = 0; parray unused; } __packed_ws__(G_BattleEndNotification_Ep3_6xB5x2E, 0x0C); @@ -7065,11 +6543,9 @@ struct G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F { } __packed_ws__(G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F, 0x8C); // 6xB5x30: Unused -// The client never sends this command, and when the client receives this -// command, it does nothing. It's likely that there was some commented-out code -// in the original source, since the handler byteswaps the command and calls -// is_online() and local_client_is_leader(), then ignores the results and -// returns immediately. +// The client never sends this command, and when the client receives this command, it does nothing. It's likely that +// there was some commented-out code in the original source, since the handler byteswaps the command and calls +// is_online() and local_client_is_leader(), then ignores the results and returns immediately. struct G_Unused_Ep3_6xB5x30 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unused_Ep3_6xB5x30) / 4, 0, 0x30, 0, 0, 0}; @@ -7133,11 +6609,10 @@ struct G_PhotonBlastStatus_Ep3_6xB4x35 { } __packed_ws__(G_PhotonBlastStatus_Ep3_6xB4x35, 0x0C); // 6xB5x36: Recreate player -// Setting client_id to a value 4 or greater while in a game causes the player -// to be temporarily replaced with a default HUmar and placed inside the central -// column in the Morgue, rendering them unable to move. The only ways out of -// this predicament appear to be either to disconnect (e.g. select Quit Game -// from the pause menu) or receive an ED (force leave game) command. +// Setting client_id to a value 4 or greater while in a game causes the player to be temporarily replaced with a +// default HUmar and placed inside the central column in the Morgue, rendering them unable to move. The only ways out +// of this predicament appear to be either to disconnect (e.g. select Quit Game from the pause menu) or receive an ED +// (force leave game) command. struct G_RecreatePlayer_Ep3_6xB5x36 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_RecreatePlayer_Ep3_6xB5x36) / 4, 0, 0x36, 0, 0, 0}; @@ -7154,10 +6629,8 @@ struct G_AdvanceFromStartingRollsPhase_Ep3_CAx37 { } __packed_ws__(G_AdvanceFromStartingRollsPhase_Ep3_CAx37, 0x14); // 6xB5x38: Card counts request -// This command causes the client identified by requested_client_id to send a -// 6xBC command to the client identified by reply_to_client_id (privately, via -// the 6D command). This is sent at the beginning of the card trade window -// sequence. +// This command causes the client identified by requested_client_id to send a 6xBC command to the client identified by +// reply_to_client_id (privately, via the 6D command). This is sent at the beginning of the card trade window sequence. struct G_CardCountsRequest_Ep3_6xB5x38 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardCountsRequest_Ep3_6xB5x38) / 4, 0, 0x38, 0, 0, 0}; @@ -7181,17 +6654,15 @@ struct G_UpdateAllPlayerStatistics_Ep3_6xB4x39 { } __packed_ws__(G_UpdateAllPlayerStatistics_Ep3_6xB4x39, 0xA8); // 6xB3x3A / CAx3A: Overall time limit expired -// It seems Sega's servers completely ignored this command and used server-side -// timing instead. newserv does the same. +// It seems Sega's servers completely ignored this command and used server-side timing instead. newserv does the same. struct G_OverallTimeLimitExpired_Ep3_CAx3A { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_OverallTimeLimitExpired_Ep3_CAx3A) / 4, 0, 0x3A, 0, 0, 0, 0, 0}; } __packed_ws__(G_OverallTimeLimitExpired_Ep3_CAx3A, 0x10); // 6xB4x3B: Load current environment -// This command is used to send spectators in a spectator team to the main -// battle. A 6xB4x05 and 6xB6x41 command should have been sent before this, to -// set the map state that should appear for the new spectator. +// This command is used to send spectators in a spectator team to the main battle. A 6xB4x05 and 6xB6x41 command should +// have been sent before this, to set the map state that should appear for the new spectator. struct G_LoadCurrentEnvironment_Ep3_6xB4x3B { G_CardBattleCommandHeader header = {0xB4, sizeof(G_LoadCurrentEnvironment_Ep3_6xB4x3B) / 4, 0, 0x3B, 0, 0, 0}; @@ -7205,17 +6676,17 @@ struct G_SetPlayerSubstatus_Ep3_6xB5x3C { // Note: header.sender_client_id specifies which client's status to update G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetPlayerSubstatus_Ep3_6xB5x3C) / 4, 0, 0x3C, 0, 0, 0}; // Status values: - // 00 (or any value not listed below) = (nothing) - // 01 = Editing - // 02 = Trading... - // 03 = At Counter + // 00 (or any value not listed below) = (nothing) + // 01 = Editing + // 02 = Trading... + // 03 = At Counter uint8_t status = 0; parray unused; } __packed_ws__(G_SetPlayerSubstatus_Ep3_6xB5x3C, 0x0C); // 6xB4x3D: Set tournament player decks -// This is sent before the counter sequence in a tournament game, to reserve the -// player and COM slots and set the map number. +// This is sent before the counter sequence in a tournament game, to reserve the player and COM slots and set the map +// number. template struct G_SetTournamentPlayerDecksT_Ep3_6xB4x3D { @@ -7258,23 +6729,21 @@ struct G_MakeCardAuctionBid_Ep3_6xB5x3E { } __packed_ws__(G_MakeCardAuctionBid_Ep3_6xB5x3E, 0x0C); // 6xB5x3F: Open blocking menu -// This command opens a shared menu between all clients in a game. The client -// specified in .client_id is able to control the menu; the other clients see -// that player's actions but cannot control anything. +// This command opens a shared menu between all clients in a game. The client specified in .client_id is able to +// control the menu; the other clients see that player's actions but cannot control anything. struct G_OpenBlockingMenu_Ep3_6xB5x3F { G_CardBattleCommandHeader header = {0xB5, sizeof(G_OpenBlockingMenu_Ep3_6xB5x3F) / 4, 0, 0x3F, 0, 0, 0}; // Menu type should be one of these values: - // 0xFF = close all menus? (TODO: verify this) - // 0x01/0x02 = battle prep menu - // 0x11 = card auction counter menu (join or cancel) - // 0x12 = go directly to card auction state (client sends EF command) + // 0xFF = close all menus? (TODO: verify this) + // 0x01/0x02 = battle prep menu + // 0x11 = card auction counter menu (join or cancel) + // 0x12 = go directly to card auction state (client sends EF command) int8_t menu_type = 0; // Must be in the range [-1, 0x14] uint8_t client_id = 0; parray unused1; - // The random_seed field is only used for the battle prep menu (01/02); for - // other menu types, it contains uninitialized data. This is used as the - // seed for a PSOV2Encryption instance, but it's not clear what that instance + // The random_seed field is only used for the battle prep menu (01/02); for other menu types, it contains + // uninitialized data. This is used as the seed for a PSOV2Encryption instance, but it's not clear what that instance // is used for (if anything). le_uint32_t random_seed = 0; parray unused2; @@ -7288,8 +6757,7 @@ struct G_MapListRequest_Ep3_CAx40 { } __packed_ws__(G_MapListRequest_Ep3_CAx40, 0x10); // 6xB3x41 / CAx41: Request map data -// The server should respond with a 6xB6x41 command containing the definition of -// the specified map. +// The server should respond with a 6xB6x41 command containing the definition of the specified map. struct G_MapDataRequest_Ep3_CAx41 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapDataRequest_Ep3_CAx41) / 4, 0, 0x41, 0, 0, 0, 0, 0}; @@ -7297,11 +6765,10 @@ struct G_MapDataRequest_Ep3_CAx41 { } __packed_ws__(G_MapDataRequest_Ep3_CAx41, 0x14); // 6xB5x42: Initiate card auction -// Sending this command to a client has the same effect as sending a 6xB5x3F -// command to tell it to open the auction menu. (This works even if the client -// doesn't have a VIP card or there are fewer than 4 players in the current -// game.) Under normal operation, the server doesn't need to do this - the -// client sends this when all of the following conditions are met: +// Sending this command to a client has the same effect as sending a 6xB5x3F command to tell it to open the auction +// menu. (This works even if the client doesn't have a VIP card or there are fewer than 4 players in the current game.) +// Under normal operation, the server doesn't need to do this - the client sends this when all of the following +// conditions are met: // 1. The client has a VIP card. (This is stored client-side in seq flag 7000.) // 2. The client is in a game with 4 players. // 3. All clients are at the auction counter. @@ -7311,17 +6778,14 @@ struct G_InitiateCardAuction_Ep3_6xB5x42 { } __packed_ws__(G_InitiateCardAuction_Ep3_6xB5x42, 8); // 6xB5x43: Unused legacy card auction -// This command stores the card IDs and counts in a global array on the client, -// but this array is never read from. The function that handles this command is -// very similar to the function that handles the EF command, so it's likely -// that this command is a now-unused early implementation of the card auction -// initiation sequence. +// This command stores the card IDs and counts in a global array on the client, but this array is never read from. The +// function that handles this command is very similar to the function that handles the EF command, so it's likely that +// this command is a now-unused early implementation of the card auction initiation sequence. struct G_UnusedLegacyCardAuction_Ep3_6xB5x43 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_UnusedLegacyCardAuction_Ep3_6xB5x43) / 4, 0, 0x43, 0, 0, 0}; struct Entry { - // Both fields here are masked. To get the actual values used by the game, - // XOR the values here with 0x39AB. + // Both fields here are masked. To get the actual values used by the game, XOR the values here with 0x39AB. le_uint16_t masked_card_id = 0xFFFF; // Must be < 0x2F1 (when unmasked) le_uint16_t masked_cost = 0; // Must be in [1, 99] (when unmasked) } __packed_ws__(Entry, 4); @@ -7339,52 +6803,43 @@ struct G_CardAuctionBidSummary_Ep3_6xB5x44 { struct G_CardAuctionResults_Ep3_6xB5x45 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionResults_Ep3_6xB5x45) / 4, 0, 0x45, 0, 0, 0}; - // This array is indexed by [card_index][client_id], and contains the final - // bid for each player on each card (or 0 if they did not bid on that card). + // This array is indexed by [card_index][client_id], and contains the final bid for each player on each card (or 0 if + // they did not bid on that card). parray, 8> bids_by_player; } __packed_ws__(G_CardAuctionResults_Ep3_6xB5x45, 0x48); // 6xB4x46: Server version strings -// This command doesn't seem to be necessary to actually play the game; the -// client just copies the included strings to global buffers and then ignores -// them. Sega's servers sent this twice for each battle, however: once after the -// initial setup phase (before starter rolls) and once when the results screen -// appeared. The second instance of this command appears to be caused by them -// recreating the TCardServer object (implemented in newserv's Episode3::Server) +// This command doesn't seem to be necessary to actually play the game; the client just copies the included strings to +// global buffers and then ignores them. Sega's servers sent this twice for each battle, however: once after the +// initial setup phase (before starter rolls) and once when the results screen appeared. The second instance of this +// command appears to be caused by them recreating the TCardServer object (implemented in newserv's Episode3::Server) // in order to support multiple sequential battles in the same team. struct G_ServerVersionStrings_Ep3_6xB4x46 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_Ep3_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; - // In all of the examples (from Sega's servers) that I've seen of this - // command, these fields have the following values: + // In all of the examples from Sega's servers that I've seen of this command, these fields have the following values: // version_signature = "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" // date_str1 = "Mar 7 2007 21:42:40" // In the client, date_str1 is different: // version_signature = "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya" // date_str1 = "Jan 21 2004 18:36:47' - // Presumably if any logs exist from before 7 March 2007, they would have a - // different date_str1, but the unchanged version_signature likely means that - // Sega never made any code changes on the server side. + // Presumably if any logs exist from before 7 March 2007, they would have a different date_str1, but the unchanged + // version_signature likely means that Sega never made any code changes on the server side. pstring version_signature; pstring date_str1; // Probably card definitions revision date - // In Sega's implementation, it seems this field is blank when starting a - // battle, and contains the current time (in the format "YYYY/MM/DD hh:mm:ss") - // when ending a battle. This may have been used for identifying debug logs. + // In Sega's implementation, it seems this field is blank when starting a battle, and contains the current time (in + // the format "YYYY/MM/DD hh:mm:ss") when ending a battle. This may have been used for identifying debug logs. pstring date_str2; - // This field contains uninitialized memory when the client generates this - // command. In normal operation, however, the client would never send this; - // it would be handled locally in offline mode, and generated by the server - // in online mode. The client completely ignores this field in either case. + // This field contains uninitialized memory when the client generates this command. In normal operation, however, the + // client would never send this; it would be handled locally in offline mode, and generated by the server in online + // mode. The client completely ignores this field in either case. le_uint32_t unused = 0; } __packed_ws__(G_ServerVersionStrings_Ep3_6xB4x46, 0xCC); struct G_ServerVersionStrings_Ep3NTE_6xB4x46 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_Ep3NTE_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; - // Ep3 NTE uses the following strings: - // "03/05/29 18:00 by K.Toya" - pstring version_signature; - // "Jun 11 2003 05:02:36" - pstring date_str1; + pstring version_signature; // "03/05/29 18:00 by K.Toya" on client + pstring date_str1; // "Jun 11 2003 05:02:36" on client } __packed_ws__(G_ServerVersionStrings_Ep3NTE_6xB4x46, 0x88); // 6xB5x47: Set online player's CARD level @@ -7405,23 +6860,19 @@ struct G_EndTurn_Ep3_CAx48 { } __packed_ws__(G_EndTurn_Ep3_CAx48, 0x14); // 6xB3x49 / CAx49: Card counts -// This command is sent when a client joins a game, but it is completely ignored -// by the original Episode 3 server. Sega presumably could have used this to -// detect the presence of unreleased cards to ban cheaters, but the effects of -// the non-saveable Have All Cards AR code don't appear in this data, so this -// would have been ineffective. There appears to be a place where Sega's server -// intended to use this data, however - the deck verification function takes a -// pointer to the card counts array, but Sega's implementation always passes -// null there, which skips the owned card count check. newserv uses this data at -// that callsite to implement one of the deck validity checks. +// This command is sent when a client joins a game, but it is completely ignored by the original Episode 3 server. Sega +// presumably could have used this to detect the presence of unreleased cards to ban cheaters, but the effects of the +// non-saveable Have All Cards AR code don't appear in this data, so this would have been ineffective. There appears to +// be a place where Sega's server intended to use this data, however - the deck verification function takes a pointer +// to the card counts array, but Sega's implementation always passes null there, which skips the owned card count +// check. newserv uses this data at that callsite to implement one of the deck validity checks. // Episode 3 Trial Edition does not send this command. struct G_CardCounts_Ep3_CAx49 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_CardCounts_Ep3_CAx49) / 4, 0, 0x49, 0, 0, 0, 0, 0}; uint8_t basis = 0; parray unused; - // This is encrypted with the trivial algorithm (see decrypt_trivial_gci_data) - // using the basis in the preceding field + // This is encrypted with the trivial algorithm (see decrypt_trivial_gci_data) using the basis in the preceding field parray card_id_to_count; } __packed_ws__(G_CardCounts_Ep3_CAx49, 0x304); @@ -7430,9 +6881,8 @@ struct G_CardCounts_Ep3_CAx49 { struct G_AddToSetCardLog_Ep3_6xB4x4A { G_CardBattleCommandHeader header = {0xB4, sizeof(G_AddToSetCardLog_Ep3_6xB4x4A) / 4, 0, 0x4A, 0, 0, 0}; - // Note: entry_count appears not to be bounds-checked; presumably the server - // could send up to 0xFF entries, but those after the 8th would not be - // byteswapped before the client handles them. + // Note: entry_count appears not to be bounds-checked; presumably the server could send up to 0xFF entries, but those + // after the 8th would not be byteswapped before the client handles them. uint8_t client_id = 0; uint8_t entry_count = 0; le_uint16_t round_num = 0; @@ -7441,26 +6891,19 @@ struct G_AddToSetCardLog_Ep3_6xB4x4A { // 6xB4x4B: Set EX result values // This command is not valid on Episode 3 Trial Edition. -// This command specifies how much EX the player should get based on the -// difference between their level and the levels of their opponents. (For -// multi-player opponent teams, the average of the opponents' levels is used.) -// The game scans the appropriate list for the entry whose threshold is less -// than or equal to than the level difference, and returns the corresponding -// value. For example, if the first two entries in the win list are {20, 40} -// and {10, 30}, and the player defeats an opponent who is 15 levels above the -// player's level, the player will get 30 EX when they win the battle. If all -// thresholds are greater than the level difference, the last entry's value is -// used. Finally, if the opponent team has no humans on it, the resulting EX -// values are divided by 2 (so in the example above, the player would only get -// 15 EX for defeating COMs). +// This command specifies how much EX the player should get based on the difference between their level and the levels +// of their opponents. (For multi-player opponent teams, the average of the opponents' levels is used.) The game scans +// the appropriate list for the entry whose threshold is less than or equal to than the level difference, and returns +// the corresponding value. For example, if the first two entries in the win list are {20, 40} and {10, 30}, and the +// player defeats an opponent who is 15 levels above the player's level, the player will get 30 EX when they win the +// battle. If all thresholds are greater than the level difference, the last entry's value is used. Finally, if the +// opponent team has no humans on it, the resulting EX values are divided by 2 (so in the example above, the player +// would only get 15 EX for defeating COMs). -// If any entry in either list has .value < -100 or > 100, the entire command is -// ignored and the EX thresholds and values are reset to their default values. -// These default values are: -// win_entries = {50, 100}, {30, 80}, {15, 70}, {10, 55}, {7, 45}, {4, 35}, -// {1, 25}, {-1, 20}, {-9, 15}, {0, 10} -// lose_entries = {1, 0}, {-2, 0}, {-3, 0}, {-4, 0}, {-5, 0}, {-6, 0}, {-7, 0}, -// {-10, -10}, {-30, -10}, {0, -15} +// If any entry in either list has .value < -100 or > 100, the entire command is ignored and the EX thresholds and +// values are reset to their default values. These default values are: +// win_entries = {50, 100}, {30, 80}, {15, 70}, {10, 55}, {7, 45}, {4, 35}, {1, 25}, {-1, 20}, {-9, 15}, {0, 10} +// lose_entries = {1, 0}, {-2, 0}, {-3, 0}, {-4, 0}, {-5, 0}, {-6, 0}, {-7, 0}, {-10, -10}, {-30, -10}, {0, -15} struct G_SetEXResultValues_Ep3_6xB4x4B { G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetEXResultValues_Ep3_6xB4x4B) / 4, 0, 0x4B, 0, 0, 0}; @@ -7512,10 +6955,9 @@ struct G_ClearSetCardConditions_Ep3_6xB4x4F { G_CardBattleCommandHeader header = {0xB4, sizeof(G_ClearSetCardConditions_Ep3_6xB4x4F) / 4, 0, 0x4F, 0, 0, 0}; uint8_t client_id = 0; uint8_t unused = 0; - // For each 1 bit in this mask, the conditions of the corresponding card - // should be deleted. The low bit corresponds to the SC card; the next bit - // corresponds to set slot 0, the next bit to set slot 1, etc. (The upper 7 - // bits of this field are unused.) + // For each 1 bit in this mask, the conditions of the corresponding card should be deleted. The low bit corresponds + // to the SC card; the next bit corresponds to set slot 0, the next bit to set slot 1, etc. (The upper 7 bits of this + // field are unused.) le_uint16_t clear_mask = 0; } __packed_ws__(G_ClearSetCardConditions_Ep3_6xB4x4F, 0x0C); @@ -7524,18 +6966,16 @@ struct G_ClearSetCardConditions_Ep3_6xB4x4F { struct G_SetTrapTileLocations_Ep3_6xB4x50 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTrapTileLocations_Ep3_6xB4x50) / 4, 0, 0x50, 0, 0, 0}; - // Each entry in this array corresponds to one of the 5 trap types, in order. - // Each entry is an [x, y] pair; if that trap type is not present, its - // location entry is FF FF. + // Each entry in this array corresponds to one of the 5 trap types, in order. Each entry is an [x, y] pair; if that + // trap type is not present, its location entry is FF FF. parray, 5> locations; parray unused; } __packed_ws__(G_SetTrapTileLocations_Ep3_6xB4x50, 0x14); // 6xB4x51: Tournament match result // This command is not valid on Episode 3 Trial Edition. -// This is sent as soon as the battle result is determined (before the battle -// results screen). If the client is in tournament mode (tournament_flag is 1 in -// the StateFlags struct), then it will use this information to show the +// This is sent as soon as the battle result is determined (before the battle results screen). If the client is in +// tournament mode (tournament_flag is 1 in the StateFlags struct), then it will use this information to show the // tournament match result screen before the battle results screen. struct G_TournamentMatchResult_Ep3_6xB4x51 { @@ -7547,59 +6987,52 @@ struct G_TournamentMatchResult_Ep3_6xB4x51 { } __packed_ws__(NamesEntry, 0x40); parray names_entries; le_uint16_t unused1 = 0; - // If round_num is equal to 6, the "On to the next battle..." text is replaced - // with "Congratulations!" and some flashier graphics. This is used for the - // final match. + // If round_num is equal to 6, the "On to the next battle..." text is replaced with "Congratulations!" and some + // flashier graphics. This is used for the final match. le_uint16_t round_num = 0; le_uint16_t num_players_per_team = 0; le_uint16_t winner_team_id = 0; le_uint32_t meseta_amount = 0; - // This field apparently is supposed to contain a %s token (as for sprintf) - // which is replaced with meseta_amount. (Yes, %s, not %d; it seems they - // manually convert it to a string for some reason before calling sprintf.) - // The results screen animates this text counting up from 0 to meseta_amount. + // This field apparently is supposed to contain a %s token (as for sprintf) which is replaced with meseta_amount. + // (Yes, %s, not %d; it seems they manually convert it to a string for some reason before calling sprintf.) The + // results screen animates this text counting up from 0 to meseta_amount. pstring meseta_reward_text; } __packed_ws__(G_TournamentMatchResult_Ep3_6xB4x51, 0xF4); // 6xB4x52: Set game metadata // This command is not valid on Episode 3 Trial Edition. -// This is sent to all players in a game and all attached spectator teams when -// any player joins or leaves any spectator team watching the same game. +// This is sent to all players in a game and all attached spectator teams when any player joins or leaves any spectator +// team watching the same game. struct G_SetGameMetadata_Ep3_6xB4x52 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetGameMetadata_Ep3_6xB4x52) / 4, 0, 0x52, 0, 0, 0}; - // This field appears before the slash in the spectators' HUD. Presumably this - // is used to indicate how many spectators are in the current spectator team. - // In the primary game (watched lobby), this is presumably unused. + // This field appears before the slash in the spectators' HUD. Presumably this is used to indicate how many + // spectators are in the current spectator team. In the primary game (watched lobby), this is presumably unused. le_uint16_t local_spectators = 0; // Clamped to [0, 999] by the client - // This field appears after the slash in the spectators' HUD. This is used to - // indicate how many spectators there are in all spectator teams attached to - // the same battle. - // This field also controls the icon shown in the primary game. If this field - // is nonzero, an icon appears in the middle of the screen during battle when - // the details view is enabled (by pressing Z). However, the number of people - // visible in the icon doesn't match the value in this field. Specifically: - // 0 = no icon - // 1 = icon with a single spectator (green) - // 2-4 = icon with 3 spectators (blue) - // 5-10 = icon with 5 spectators (yellow) - // 11-29 = icon with 8 spectators (purple) - // 30+ = icon with 12 spectators (red) + // This field appears after the slash in the spectators' HUD. This is used to indicate how many spectators there are + // in all spectator teams attached to the same battle. This field also controls the icon shown in the primary game; + // if it's nonzero, an icon appears in the middle of the screen during battle when the details view is enabled (by + // pressing Z). However, the number of people visible in the icon doesn't match the value in this field: + // 0 spectators = no icon + // 1 spectators = icon with a single spectator (green) + // 2-4 spectators = icon with 3 spectators (blue) + // 5-10 spectators = icon with 5 spectators (yellow) + // 11-29 spectators = icon with 8 spectators (purple) + // 30+ spectators = icon with 12 spectators (red) le_uint16_t total_spectators = 0; // Clamped to [0, 999] by the client le_uint16_t unused = 0; - // If text_size is not zero, the text is shown in the top bar instead of the - // usual message ("Viewing Battle", "Time left: XX:XX", and the like). + // If text_size is not zero, the text is shown in the top bar instead of the usual message ("Viewing Battle", "Time + // left: XX:XX", and the like). le_uint16_t text_size = 0; pstring text; } __packed_ws__(G_SetGameMetadata_Ep3_6xB4x52, 0x110); // 6xB4x53: Reject battle start request // This command is not valid on Episode 3 Trial Edition. -// This is sent in response to a CAx1D command if setup isn't complete (e.g. if -// some names/decks are missing or invalid). Under normal operation, this should -// never happen. -// Note: It seems the client ignores everything in this structure; the command -// handler just sets a global state flag and returns immediately. +// This is sent in response to a CAx1D command if setup isn't complete (e.g. if some names/decks are missing or +// invalid). Under normal operation, this should never happen. +// Note: It seems the client ignores everything in this structure; the command handler just sets a global state flag +// and returns immediately. struct G_RejectBattleStartRequest_Ep3_6xB4x53 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_RejectBattleStartRequest_Ep3_6xB4x53) / 4, 0, 0x53, 0, 0, 0}; @@ -7609,14 +7042,14 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 { Episode3::MapAndRulesState state; } __packed_ws__(G_RejectBattleStartRequest_Ep3_6xB4x53, 0x144); -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// EXTENDED COMMANDS /////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EXTENDED COMMANDS ////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// These commands are not part of the official protocol; newserv uses these -// along with client functions to implement extended functionality. +// These commands are not part of the official protocol; newserv uses these along with client functions to implement +// extended functionality. // 30 (C->S): Extended player info // Requested with the GetExtendedPlayerInfo patch. Format depends on version: @@ -7625,9 +7058,8 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 { // XB v3: PSOXBCharacterFile::Character // 6xE4: Increment enemy damage -// This command increments or decrements the amount of damage an enemy has -// sustained. This replaces the use of total_damage in 6x0A to update enemy HP -// when used with the EnemyDamageSync patch. +// This command increments or decrements the amount of damage an enemy has sustained. This replaces the use of +// total_damage in 6x0A to update enemy HP when used with the EnemyDamageSync patch. struct G_IncrementEnemyDamage_Extension_6xE4 { /* 00 */ G_EntityIDHeader header = {0xE4, sizeof(G_IncrementEnemyDamage_Extension_6xE4) / 4, 0x0000}; diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index cd8c86a5..6af42856 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -113,17 +113,14 @@ enum class CardRank : uint8_t { R3 = 0x09, R4 = 0x0A, SS = 0x0B, - // Cards with the D1 or D2 ranks are considered never usable by the player, - // and are automatically removed from player decks before battle and when - // loading the deckbuilder. Cards with the D1 rank appear in the deckbuilder - // but are grayed out (and cannot be added to decks); cards with the D2 rank - // don't appear in the deckbuilder at all. + // Cards with the D1 or D2 ranks are considered never usable by the player, and are automatically removed from player + // decks before battle and when loading the deckbuilder. Cards with the D1 rank appear in the deck builder but are + // grayed out (and cannot be added to decks); cards with the D2 rank don't appear in the deckbuilder at all. D1 = 0x0C, D2 = 0x0D, - // The D3 rank is referenced in a few places, including the function that - // determines whether or not a card can appear in post-battle draws, and the - // function that determines whether a card should appear in the deckbuilder. - // In these cases, it prevents the card from appearing. + // The D3 rank is referenced in a few places, including the function that determines whether or not a card can appear + // in post-battle draws, and the function that determines whether a card should appear in the deckbuilder. In these + // cases, it prevents the card from appearing. D3 = 0x0E, }; @@ -221,7 +218,7 @@ enum class ConditionType : uint8_t { FREE_MANEUVER = 0x27, // Enable movement over occupied tiles SCALE_MV_COST = 0x28, // Multiply all move action costs by expr (which may be zero) CLONE = 0x29, // Make setting this card free if at least one card of type N is already on the field - DEF_DISABLE_BY_COST = 0x2A, // Disable use of any defense cards costing between (N / 10) and (N % 10) points, inclusive + DEF_DISABLE_BY_COST = 0x2A, // Disable use of defense cards costing between (N / 10) and (N % 10) points, inclusive FILIAL = 0x2B, // Increase controlling SC's HP by N when this card is destroyed SNATCH = 0x2C, // Steal N EXP during attack HAND_DISRUPTER = 0x2D, // Discard N cards from hand immediately @@ -259,7 +256,7 @@ enum class ConditionType : uint8_t { HEAVY_RAMPAGE = 0x4D, // If SC has 3 or more items equipped, attacks target SC and all equipped items AP_GROWTH = 0x4E, // Permanently increase AP by N TP_GROWTH = 0x4F, // Permanently increase TP by N - REBORN = 0x50, // If any card of type N is on the field, this card goes to the hand when destroyed instead of being discarded + REBORN = 0x50, // If any card of type N is on the field, this card goes back to the hand when destroyed COPY = 0x51, // Temporarily set AP/TP to N percent (or 100% if N is 0) of opponent's values UNKNOWN_52 = 0x52, MISC_GUARDS = 0x53, // Add N to card's defense value @@ -272,7 +269,7 @@ enum class ConditionType : uint8_t { ANTI_ABNORMALITY_2 = 0x5A, // Remove all conditions FIXED_RANGE = 0x5B, // Use SC's range instead of weapon or attack card ranges ELUDE = 0x5C, // SC does not lose HP when equipped items are destroyed - PARRY = 0x5D, // Forward attack to a random FC within one tile of original target, excluding attacker and original target + PARRY = 0x5D, // Forward attack to random FC within one tile of original target, except attacker and original target BLOCK_ATTACK = 0x5E, // Completely block attack UNKNOWN_5F = 0x5F, UNKNOWN_60 = 0x60, @@ -513,16 +510,14 @@ struct CardDefinition { } __packed_ws__(Stat, 4); struct Effect { - // effect_num is the 1-based index of this effect within the card definition - // (that is, .effects[0] should have effect_num == 1 if it is used). + // effect_num is the 1-based index of this effect within the card definition (that is, .effects[0] should have + // effect_num == 1 if it is used). /* 00 */ uint8_t effect_num; /* 01 */ ConditionType type; - // For ConditionTypes that need it, expr specifies "how much". (For those - // that don't, expr may be blank.) The value may contain tokens that refer - // to stats from the current battle (see description_for_expr_token) and - // operators to perform basic computations on them. Operators are evaluated - // left-to-right in the expression, and there are no operator precedence - // rules; for example, the expression "4+4//2" results in 4, not 6. + // For ConditionTypes that need it, expr specifies "how much". (For those that don't, expr may be blank.) The value + // may contain tokens that refer to stats from the current battle (see description_for_expr_token) and operators to + // perform basic computations on them. Operators are evaluated left-to-right in the expression, and there are no + // operator precedence rules; for example, the expression "4+4//2" results in 4, not 6. /* 02 */ pstring expr; // when specifies in which phase the effect should activate. /* 11 */ EffectWhen when; @@ -532,13 +527,11 @@ struct CardDefinition { /* 16 */ pstring arg2; // arg3 generally specifies who is targeted by the effect. /* 1A */ pstring arg3; - // apply_criterion can be used to apply an additional condition for when the - // effect should activate. For example, it can be used to make the effect - // only activate if the target is not a Story Character. + // apply_criterion can be used to apply an additional condition for when the effect should activate. For example, + // it can be used to make the effect only activate if the target is not a Story Character. /* 1E */ CriterionCode apply_criterion; - // name_index specifies which string from TextEnglish.pr2 is shown next to - // the card when it is attacking or defending. Zero in this field means no - // string is shown for this ability. + // name_index specifies which string from TextEnglish.pr2 is shown next to the card when it is attacking or + // defending. Zero in this field means no string is shown for this ability. /* 1F */ uint8_t name_index; /* 20 */ @@ -551,16 +544,16 @@ struct CardDefinition { /* 0000 */ be_uint32_t card_id; /* 0004 */ pstring jp_name; - // The list of card definitions ends with a "sentinel" definition that isn't a - // real card, but instead has a negative number in the type field here. + // The list of card definitions ends with a sentinel definition that isn't a real card, but instead has a negative + // number in the type field here. /* 0044 */ CardType type; /* 0045 */ uint8_t self_cost; // ATK dice points required /* 0046 */ uint8_t ally_cost; // ATK points from allies required; PBs use this /* 0047 */ uint8_t unused1; - // In the definitions file, only .code is populated here; .decode_code() must - // be called to fill in .type and .stat within each of these. + // In the definitions file, only .code is populated here; .decode_code() must be called to fill in .type and .stat + // within each of these. /* 0048 */ Stat hp; /* 004C */ Stat ap; /* 0050 */ Stat tp; @@ -571,195 +564,158 @@ struct CardDefinition { /* 0060 */ parray right_colors; /* 0068 */ parray top_colors; - // The card's attack range is defined in a somewhat odd format here. Each - // field in this array corresponds to a single row of the range, and every - // fourth bit, starting with bit 15, corresponds to a tile in that row. The - // rest of the bits are ignored, except in two special cases described below. - // For example, Ohgun's range is: + // The card's attack range is defined in a somewhat odd format here. Each field in this array corresponds to a single + // row of the range, and every fourth bit, starting with bit 15, corresponds to a tile in that row. The rest of the + // bits are ignored, except in two special cases described below. For example, Ohgun's range is: // [0] = 0x00000000 => ----- // [1] = 0x00001110 => -***- // [2] = 0x00001110 => -***- // [3] = 0x00000000 => ----- // [4] = 0x00000000 => ----- (the card itself is in the center of this row) // [5] = 0x00000000 => ----- - // // The two special cases are as follows: - // 1. If all six values in the range array are 0x000FFFFF, then the card's - // range is the entire field. - // 2. If the cell corresponding to the card itself ((range[4] >> 8) & 0x0F) is - // not zero, then the rest of the range array is ignored and the card's - // range comes from a fixed set of ranges instead. See decode_range() for - // more information. + // 1. If all six values in the range array are 0x000FFFFF, then the card's range is the entire field. + // 2. If the cell corresponding to the card itself ((range[4] >> 8) & 0x0F) is not zero, then the rest of the range + // array is ignored and the card's range comes from a fixed set of ranges instead. See decode_range() for details. /* 0070 */ parray range; /* 0088 */ be_uint32_t unused2; /* 008C */ TargetMode target_mode; /* 008D */ uint8_t assist_turns; // 90 (dec) = once, 99 (dec) = forever - // This field is 1 if the card cannot move by itself. Item cards hare 1 here - // because they cannot move on their own and automatically move along with - // their SC instead. Generally only SCs and creatures have 0 here. + // This field is 1 if the card cannot move by itself. Item cards hare 1 here because they cannot move on their own + // and automatically move along with their SC instead. Generally only SCs and creatures have 0 here. /* 008E */ uint8_t cannot_move; - // This field is 1 if the card cannot take part in an attack. Unlike - // cannot_move, cards that cannot attack on their own but can take part in an - // attack (such as action cards) have 0 here. Most shields, mags, defense - // actions, and assist cards have 1 here. + // This field is 1 if the card cannot take part in an attack. Unlike cannot_move, cards that cannot attack on their + // own but can take part in an attack (such as action cards) have 0 here. Most shields, mags, defense actions, and + // assist cards have 1 here. /* 008F */ uint8_t cannot_attack; /* 0090 */ uint8_t unused3; - // If cannot_drop is 1, this card can't appear in post-battle rewards and is - // considered unobtainable by players, so the game will remove it from the - // player's collection if they have any copies of it. A value of 1 here also + // If cannot_drop is 1, this card can't appear in post-battle rewards and is considered unobtainable by players, so + // the game will remove it from the player's collection if they have any copies of it. A value of 1 here also // prevents the card from being used as a God Whim random assist. /* 0091 */ uint8_t cannot_drop; - // This criterion code specifies who can use the card, and when it can be - // used. This specifies which Hero-side SCs can use which items, for example, - // and when action cards can be played (when SC or FC is attacking, on self or + // This criterion code specifies who can use the card, and when it can be used. This specifies which Hero-side SCs + // can use which items, for example, and when action cards can be played (when SC or FC is attacking, on self or // ally, etc.). /* 0092 */ CriterionCode usable_criterion; /* 0093 */ CardRank rank; /* 0094 */ be_uint16_t unused4; - // The card class is used for checking attributes (e.g. item types). It's - // stored big-endian here, so there's a helper function (card_class()) that - // returns a usable CardClass enum value. + // The card class is used for checking attributes (e.g. item types). It's stored big-endian here, so there's a helper + // function (card_class()) that returns a usable CardClass enum value. /* 0096 */ be_uint16_t be_card_class; - // If this card is an assist card, this field controls how COM players handle - // playing it. This field is ignored for non-assist cards. This integer - // encodes the following fields: - // - assist_ai_params % 100 (that is, the two lowest decimal places) appears - // to specify the effect, though a few unrelated cards share values in this - // field. It's not yet known how exactly this is used by the COM logic. - // - (assist_ai_params / 100) % 10 specifies the priority. It appears the COM - // logic always chooses the assist card with the highest value in this field - // if there are multiple cards to consider. - // - (assist_ai_params / 1000) % 10 specifies on whom the assist card may be - // played (0 = any player, 1 = self, 2 = self or ally, 3 = enemy only). + // If this card is an assist card, this field controls how COM players handle playing it. This field is ignored for + // non-assist cards. This integer encodes the following fields: + // - assist_ai_params % 100 (that is, the two lowest decimal places) appears to specify the effect, though a few + // unrelated cards share values in this field. It's not yet known how exactly this is used by the COM logic. + // - (assist_ai_params / 100) % 10 specifies the priority. It appears the COM logic always chooses the assist card + // with the highest value in this field if there are multiple cards to consider. + // - (assist_ai_params / 1000) % 10 specifies on whom the assist card may be played (0 = any player, 1 = self, 2 = + // self or ally, 3 = enemy only). /* 0098 */ be_uint16_t assist_ai_params; - // Most cards in the official definitions file have the same value stored in - // unused5 as in assist_ai_params. Unlike assist_ai_params, unused5 does not - // appear to be used anywhere. + // Most cards in the official definitions file have the same value stored in unused5 as in assist_ai_params. Unlike + // assist_ai_params, unused5 does not appear to be used anywhere. /* 009A */ be_uint16_t unused5; - // The card drop rates control how likely the card is to appear in a standard - // post-battle random draw. How this works is fairly complex and is explained - // below in detail. Before any of that logic, this card can never drop and no - // card can transform into this card if any of the following are true: + // The card drop rates control how likely the card is to appear in a standard post-battle random draw. How this works + // is fairly complex and is explained below in detail. Before any of that logic, this card can never drop and no card + // can transform into this card if any of the following are true: // - type is SC_HUNTERS or SC_ARKZ // - card_class is BOSS_ATTACK_ACTION (0x23) or BOSS_TECH (0x24) // - rank is E, D1, or D2 - // - cannot_drop is 1 (specifically 1; other nonzero values for cannot_drop - // don't prevent the card from appearing in post-battle draws) + // - cannot_drop is 1 (specifically 1; other nonzero values don't prevent the card from appearing in draws) // If none of these conditions apply, the logic below is used. // // Drop rates are integers which encode the following data: - // - rate % 10 (that is, the lowest decimal place) specifies the required game - // mode. 0 means any mode, 1 means offline story mode, 2 means 1P free - // battle, 3 means 2P+ free battle (specifically, PvP - two humans vs. two - // COMs counts as 1P free battle), 4 means online mode, 5 means tournament. - // Some cards have this field set to 6, which isn't a valid game mode; it - // seems Sega used this as a way to make sure the drop rate never applies. - // - (rate / 10) % 100 (that is, the tens and hundreds decimal places) specify - // the environment number + 1. For example, if this field contains 5, then - // this drop only applies if the battle took place at Molae Venti - // (environment number 4). If this field is zero, the drop applies - // regardless of where the battle took place. - // - rate / 1000 (the thousands decimal place) specifies the rarity class. - // This can be any number in the range [0, 9], and affects how likely the - // card is to appear based on the player's level. See below for details. - // - rate / 10000 (the ten-thousands decimal place) specifies if the drop rate - // applies only if the player used a Hunters deck (1), only if they used an - // Arkz deck (2), or if they used any deck (0). + // - rate % 10 (that is, the lowest decimal place) specifies the required game mode. 0 means any mode, 1 means + // offline story mode, 2 means 1P free battle, 3 means 2P+ free battle (specifically, PvP - two humans vs. two COMs + // counts as 1P free battle), 4 means online mode, 5 means tournament. Some cards have this field set to 6, which + // isn't a valid game mode; it seems Sega used this as a way to make sure the drop rate never applies. + // - (rate / 10) % 100 (that is, the tens and hundreds decimal places) specify the environment number + 1. For + // example, if this field contains 5, then this drop only applies if the battle took place at Molae Venti + // (environment number 4). If this field is zero, the drop applies regardless of where the battle took place. + // - rate / 1000 (the thousands decimal place) specifies the rarity class. This can be any number in the range [0, + // 9], and affects how likely the card is to appear based on the player's level. See below for details. + // - rate / 10000 (the ten-thousands decimal place) specifies if the drop rate applies only if the player used a + // Hunters deck (1), only if they used an Arkz deck (2), or if they used any deck (0). // - // When determining which cards to drop, the game first checks the drop rate - // fields on all cards. For each drop rate that applies, the game adds the - // card ID into an appropriate bucket based on the rarity class. (If both drop - // rates for a card apply, the card ID is added twice.) The player's level - // class is then computed according to the following table: - // 1 2 3 4 5 6 7 8 9 10 - // CLvOff 1-2 3-4 5-9 10-14 15-19 20-25 26-29 30-39 40-49 50+ - // CLvOn 1-2 3-4 5-10 11-16 17-23 24-32 33-39 40-49 50-99 100+ - // For the purposes of this computation, the player's level is used by default - // (CLvOn or CLvOff), but the map may override it - see win_level_override and - // loss_level_override in MapDefinition. This specifies which row in the + // When determining which cards to drop, the game first checks the drop rate fields on all cards. For each drop rate + // that applies, the game adds the card ID into an appropriate bucket based on the rarity class. (If both drop rates + // for a card apply, the card ID is added twice.) The player's level class is then computed according to the + // following table: + // 1 2 3 4 5 6 7 8 9 10 + // CLvOff 1-2 3-4 5-9 10-14 15-19 20-25 26-29 30-39 40-49 50+ + // CLvOn 1-2 3-4 5-10 11-16 17-23 24-32 33-39 40-49 50-99 100+ + // For the purposes of this computation, the player's level is used by default (CLvOn or CLvOff), but the map may + // override it - see win_level_override and loss_level_override in MapDefinition. This specifies which row in the // following tables will be used. // - // Cards are then chosen from the buckets with a weighted distribution - // according to these tables (row is player's level class, column is card's - // rarity class): - // Offline: - // LC | RC = 0 1 2 3 4 5 6 7 8 9 - // 1 | 8000 2000 50 - // 2 | 6000 3500 500 20 - // 3 | 4500 4000 1500 200 - // 4 | 3500 3500 2300 700 20 - // 5 | 2700 2800 2500 1500 500 10 - // 6 | 2300 2300 2300 1900 900 300 1 - // 7 | 1995 2100 2100 2100 1000 700 5 - // 8 | 1789 2100 2100 2100 1100 800 10 1 - // 9 | 14620 20000 21000 22000 13000 9000 300 80 - // 10 | 133997 190000 200000 200000 150000 120000 5000 1000 2 1 - // Online: - // LC | RC = 0 1 2 3 4 5 6 7 8 9 - // 1 | 8000 2000 50 - // 2 | 6000 3500 500 50 - // 3 | 4500 3500 1500 400 100 - // 4 | 3000 3000 2500 1000 450 50 - // 5 | 2000 2600 2750 2000 500 100 50 - // 6 | 1900 2200 2500 2100 830 350 100 20 - // 7 | 1900 2000 2000 2000 1000 500 500 100 - // 8 | 160000 160000 190000 190000 130000 100000 50000 19999 1 - // 9 | 120000 120000 150000 160000 150000 150000 100000 49989 10 1 - // 10 | 120000 120000 130000 150000 160000 150000 100000 69965 30 5 - // These values are all relative to other values in the same row. For example, - // if your character is in level class 1, you'll get cards of rarity class 0 - // about 80% of the time, cards of rarity class 1 about 20% of the time, and - // cards of rarity class 2 about 0.5% of the time. (The actual probabilities - // are 8000/10050, 2000/10050, and 50/10050.) + // Cards are then chosen from the buckets with a weighted distribution according to these tables (row is player's + // level class, column is card's rarity class): + // Offline: + // LC | RC = 0 1 2 3 4 5 6 7 8 9 + // 1 | 8000 2000 50 + // 2 | 6000 3500 500 20 + // 3 | 4500 4000 1500 200 + // 4 | 3500 3500 2300 700 20 + // 5 | 2700 2800 2500 1500 500 10 + // 6 | 2300 2300 2300 1900 900 300 1 + // 7 | 1995 2100 2100 2100 1000 700 5 + // 8 | 1789 2100 2100 2100 1100 800 10 1 + // 9 | 14620 20000 21000 22000 13000 9000 300 80 + // 10 | 133997 190000 200000 200000 150000 120000 5000 1000 2 1 + // Online: + // LC | RC = 0 1 2 3 4 5 6 7 8 9 + // 1 | 8000 2000 50 + // 2 | 6000 3500 500 50 + // 3 | 4500 3500 1500 400 100 + // 4 | 3000 3000 2500 1000 450 50 + // 5 | 2000 2600 2750 2000 500 100 50 + // 6 | 1900 2200 2500 2100 830 350 100 20 + // 7 | 1900 2000 2000 2000 1000 500 500 100 + // 8 | 160000 160000 190000 190000 130000 100000 50000 19999 1 + // 9 | 120000 120000 150000 160000 150000 150000 100000 49989 10 1 + // 10 | 120000 120000 130000 150000 160000 150000 100000 69965 30 5 + // These values are all relative to other values in the same row. For example, if your character is in level class 1, + // you'll get cards of rarity class 0 about 80% of the time, cards of rarity class 1 about 20% of the time, and cards + // of rarity class 2 about 0.5% of the time. (The actual probabilities are 8000/10050, 2000/10050, and 50/10050.) // - // When choosing the contents of the four card packs after a battle, the game - // first chooses how many cards to give the player based on the end-of-battle - // rank (9 for S, 8 for A+/A, 7 for B+/B, 6 for C+/C, 5 for D+/D/E, 2 if the - // player lost). It then decides the number of "restricted" cards; if the - // player is getting 6 or more cards, there are 2 restricted cards per pack, - // otherwise there is only one. The restricted cards are required to be a - // certain type in each pack except the black pack: + // When choosing the contents of the four card packs after a battle, the game first chooses how many cards to give + // the player based on the end-of-battle rank (9 for S, 8 for A+/A, 7 for B+/B, 6 for C+/C, 5 for D+/D/E, 2 if the + // player lost). It then decides the number of "restricted" cards; if the player is getting 6 or more cards, there + // are 2 restricted cards per pack, otherwise there is only one. The restricted cards are required to be a certain + // type in each pack except the black pack: // - In the blue pack, the restricted cards must be creature cards. // - In the red pack, the restricted cards must be item cards. // - In the green pack, the restricted cards must be action cards. - // For example, if you get a B+ rank after winning a battle and pick the green - // pack, you will always get at least two action cards. + // For example, if you get a B+ rank after winning a battle and pick the green pack, you will always get at least two + // action cards. // - // The game then samples N card IDs from the appropriate buckets (where N is - // the number chosen above), but for the first 1 or 2 cards, it applies the - // restriction described above and re-draws if the card is the wrong type. - // After sampling the N card IDs, it sorts them and presents them to the - // player. + // The game then samples N card IDs from the appropriate buckets (where N is the number chosen above), but for the + // first 1 or 2 cards, it applies the restriction described above and re-draws if the card is the wrong type. After + // sampling the N card IDs, it sorts them and presents them to the player. // - // There is one more effect to consider after cards are chosen: cards may - // randomly transform into VIP cards or into stronger (and rarer) cards. The - // chance of each of these occurring is based on the rarity of that card that - // may transform, and the number of copies of that card which the player - // already has. In the below table, P(activate) is the probability that any - // transformation is attempted at all; if this check passes, the player sees - // the glow effect and "The change will occur..." text. P(vip) is the - // probability that the card becomes a VIP card, after the glow effect plays. - // P(rare) is the probability of the card becoming a rarer card after the glow - // effect. Therefore, the final probability that a card will transform into a - // VIP card is P(activate) * P(vip), and the final probability of transforming - // into a rarer card is P(activate) * P(rare). - // ======== Card rank N4-N1 ======== ======== Card rank R4-R1 ======== - // Count P(activate) P(rare) P(vip) P(activate) P(rare) P(vip) - // 0-4 0% 0% 0% 0% 0% 0% - // 5-10 1.923077% 55% 0.5% 2.0408163% 55% 0.5% - // 11-16 2.1276595% 60% 0.45454544% 2.2727273% 60% 0.4761905% - // 17-24 2.3809524% 70% 0.4347826% 2.5641026% 70% 0.45454544% - // 25-32 2.7027028% 70% 0.4% 2.9411765% 70% 0.5% - // 33-40 3.125% 80% 0.38461538% 3.448276% 70% 0.5% - // 41-52 3.7037037% 80% 0.35714286% 4.1666668% 80% 0.45454544% - // 53-99 5% 90% 0.33333334% 5.263158% 90% 0.4347826% + // There is one more effect to consider after cards are chosen: cards may randomly transform into VIP cards or into + // stronger (and rarer) cards. The chance of each of these occurring is based on the rarity of that card that may + // transform, and the number of copies of that card which the player already has. In the below table, P(activate) is + // the probability that any transformation is attempted at all; if this check passes, the player sees the glow effect + // and "The change will occur..." text. P(vip) is the probability that the card becomes a VIP card, after the glow + // effect plays. P(rare) is the probability of the card becoming a rarer card after the glow effect. Therefore, the + // final probability that a card will transform into a VIP card is P(activate) * P(vip), and the final probability of + // transforming into a rarer card is P(activate) * P(rare). + // ======== Card rank N4-N1 ======== ======== Card rank R4-R1 ======== + // Count P(activate) P(rare) P(vip) P(activate) P(rare) P(vip) + // 0-4 0% 0% 0% 0% 0% 0% + // 5-10 1.923077% 55% 0.5% 2.0408163% 55% 0.5% + // 11-16 2.1276595% 60% 0.45454544% 2.2727273% 60% 0.4761905% + // 17-24 2.3809524% 70% 0.4347826% 2.5641026% 70% 0.45454544% + // 25-32 2.7027028% 70% 0.4% 2.9411765% 70% 0.5% + // 33-40 3.125% 80% 0.38461538% 3.448276% 70% 0.5% + // 41-52 3.7037037% 80% 0.35714286% 4.1666668% 80% 0.45454544% + // 53-99 5% 90% 0.33333334% 5.263158% 90% 0.4347826% // - // If a transformation occurs, the card transforms to a card of a different - // rank. First, the game consults the following table to determine the rank of - // the resulting card (original card's rank on the left, new card's rank + // If a transformation occurs, the card transforms to a card of a different rank. First, the game consults the + // following table to determine the rank of the resulting card (original card's rank on the left, new card's rank // across the top): // N4 N3 N2 N1 R4 R3 R2 R1 S SS // N4 => 60 30 10 @@ -770,37 +726,30 @@ struct CardDefinition { // R3 => 20 50 28 1 // R2 => 30 60 5 // R1 => 900 100 1 - // For example, when an N2 card transforms, there is a 60% chance to become - // R4, a 30% chance to become R3, and a 10% chance to become R2. When an R1 - // card transforms, there is a 900/1001 chance of becoming another R1, a - // 100/1001 chance of becoming an S, and a 1/1001 chance of becoming an SS. + // For example, when an N2 card transforms, there is a 60% chance to become R4, a 30% chance to become R3, and a 10% + // chance to become R2. When an R1 card transforms, there is a 900/1001 chance of becoming another R1, a 100/1001 + // chance of becoming an S, and a 1/1001 chance of becoming an SS. // - // Once a rank is chosen, the game puts all possible cards into buckets based - // on how many of that card the player already has, then chooses a random card - // out of bucket 0, then bucket 1, etc. all the way up to bucket 49 (or 2 if - // the final rank is S or SS). The first drawn card that has the final rank is - // the card that the original card transforms into. Notably, this logic means - // that cards are more likely to transform into cards that the player doesn't - // already have, or only has few copies of. Also notably, it is impossible for - // a card to transform into another card that the player already has 50 or - // more copies of, or an S or SS card that the player already has 3 copies of. + // Once a rank is chosen, the game puts all possible cards into buckets based on how many of that card the player + // already has, then chooses a random card out of bucket 0, then bucket 1, etc. all the way up to bucket 49 (or 2 if + // the final rank is S or SS). The first drawn card that has the final rank is the card that the original card + // transforms into. Notably, this logic means that cards are more likely to transform into cards that the player + // doesn't already have, or only has few copies of. Also notably, it is impossible for a card to transform into + // another card that the player already has 50 or more copies of, or an S or SS card that the player already has 3 + // copies of. // - // One curiosity about the above procedure is that the buckets can only hold - // 400 cards each for the N ranks, 300 each for the R ranks, and 100 each for - // S and SS. It is possible for one bucket to overflow into the next, or to - // overflow out of bounds and cause memory corruption, if there are (for - // example) more than 400 cards that have ranks N1-N4, and the player has 99 - // of all of them. + // One curiosity about the above procedure is that the buckets can only hold 400 cards each for the N ranks, 300 each + // for the R ranks, and 100 each for S and SS. It is possible for one bucket to overflow into the next, or to + // overflow out of bounds and cause memory corruption, if there are (for example) more than 400 cards that have ranks + // N1-N4, and the player has 99 of all of them. // - // Remember the drop rates mentioned way back in the second paragraph of this - // enormous comment? That's what this array stores. + // Remember the drop rates from back in the second paragraph of this enormous comment? That's what this array stores. /* 009C */ parray drop_rates; /* 00A0 */ pstring en_name; /* 00B4 */ pstring jp_short_name; /* 00BF */ pstring en_short_name; - // These effects modify the card's behavior in various situations. Only - // effects for which effect_num is not zero are used. + // These effects modify the card's behavior in various situations. Only entries where effect_num != 0 are used. /* 00C7 */ parray effects; /* 0127 */ uint8_t unused6; /* 0128 */ @@ -829,10 +778,9 @@ struct CardDefinitionsFooter { struct DeckDefinition { /* 00 */ pstring name; /* 10 */ be_uint32_t client_id; // 0-3 - // List of card IDs. The card count is the number of nonzero entries here - // before a zero entry (or 50 if no entries are nonzero). The first card ID is - // the SC card, which the game implicitly subtracts from the limit - so a - // valid deck should actually have 31 cards in it. + // List of card IDs. The card count is the number of nonzero entries here before a zero entry (or 50 if no entries + // are nonzero). The first card ID is the SC card, which the game implicitly subtracts from the limit - so a valid + // deck should actually have 31 cards in it. /* 14 */ parray card_ids; /* 78 */ be_uint32_t unknown_a1; // Last modification time @@ -847,64 +795,49 @@ struct DeckDefinition { } __packed_ws__(DeckDefinition, 0x84); struct PlayerConfig { - // The game splits this internally into two structures. The first column of - // offsets is relative to the start of the first structure; the second column - // is relative to the start of the second structure. + // The game splits this internally into two structures. The first column of offsets is relative to the start of the + // first structure; the second column is relative to the start of the second structure. /* 0000:---- */ pstring rank_text; // From B7 command /* 000C:---- */ parray unknown_a1; /* 0028:---- */ parray tech_menu_shortcut_entries; /* 0050:---- */ parray choice_search_config; // This field maps to quest_counters on Episodes 1 & 2 /* 0078:---- */ parray scenario_progress; - // place_counts[0] and [1] from this field are added to the player's win and - // loss count respectively when they're shown in the status menu. However, - // these values start at 0 and never seem to be modified. Perhaps in an - // earlier version, this was the offline records structure, but they later - // decided to just count online and offline records together in the main - // records structure and didn't remove the codepath that reads from this. + // place_counts[0] and [1] from this field are added to the player's win and loss count respectively when they're + // shown in the status menu. However, these values start at 0 and never seem to be modified. Perhaps in an earlier + // version, this was the offline records structure, but they later decided to just count online and offline records + // together in the main records structure and didn't remove the codepath that reads from this. /* 0138:---- */ PlayerRecordsBattleBE unused_offline_records; /* 0150:---- */ parray unknown_a4; - // The PlayerDataSegment structure begins here. In newserv, we combine this - // structure into PlayerConfig since the two are always used together on the - // server side. + // The PlayerDataSegment structure begins here. In newserv, we combine this structure into PlayerConfig since the two + // are always used together on the server side. /* 0154:0000 */ uint8_t is_encrypted; /* 0155:0001 */ uint8_t basis; /* 0156:0002 */ parray unused; - // The following fields (here through the beginning of decks) are encrypted - // using the trivial algorithm, with the basis specified above, if - // is_encrypted is equal to 1. - // It appears the card counts field in this structure is actually 1000 (0x3E8) - // bytes long, even though in every other place the counts array appears it's - // 0x2F0 bytes long. They presumably did this because of the checksum logic. + // The following fields (here through the beginning of decks) are encrypted using the trivial algorithm, with the + // basis specified above, if is_encrypted is equal to 1. + // It appears the card counts field in this structure is actually 1000 (0x3E8) bytes long, even though in every other + // place the counts array appears it's 0x2F0 bytes long. They presumably did this because of the checksum logic. /* 0158:0004 */ parray card_counts; - // These appear to be an attempt at checksumming the card counts array, but - // the algorithm doesn't cover the entire array and instead reads from later - // parts of this structure. This appears to be due to a copy/paste error in - // the original code. The algorithm sums card_counts [0] through [19] and puts - // the result in card_count_checksums[0], then sums card counts [50] through - // [69] and puts the result in card_count_checksums[1], etc. Presumably they - // intended to use 20 as the stride instead of 50, which would have exactly - // covered the entire card_counts array. + // These appear to be an attempt at checksumming the card counts array, but the algorithm doesn't cover the entire + // array and instead reads from later parts of this structure. This appears to be due to a copy/paste error in the + // original code. The algorithm sums card_counts [0] through [19] and puts the result in card_count_checksums[0], + // then sums card counts [50] through [69] and puts the result in card_count_checksums[1], etc. Presumably they + // intended to use 20 as the stride instead of 50, which would have exactly covered the entire card_counts array. /* 0540:03EC */ parray card_count_checksums; - // These 64-bit integers encode information about when rare cards (those with - // ranks S, SS, E, or D2) were obtained. Each integer contains the following - // fields: + // These 64-bit integers encode information about when rare cards (those with ranks S, SS, E, or D2) were obtained. + // Each integer contains the following bit fields: // ???????? PPPPPPPP PPPPVVVV VVVVVVVV VVVVVVVV VVVVVVVV VVVVVVVV VVVVVVVV - // The meaning of the high byte is unknown, but it is not used by the decoding - // function. P is a prime number between 1009 (0x3F1) and 2039 (0x7F7), - // inclusive. V is a 44-bit integer that, when modulated by P, yields the card - // ID (that is, V % P == card_id). When a non-rare card is obtained or lost, - // the game just increments or decrements the value in the card_counts array - // above, but when a rare card is obtained or lost, the game adds or removes a - // token in rare_tokens and recomputes the count for that card by scanning and - // decoding all rare tokens. It then writes that count to card_counts. - // This seems to be an anti-cheating measure specifically targeted at memory - // editing - the server could verify that the count in card_counts is correct - // for rare cards by counting the valid tokens in this array. (Sega seemed - // fairly concerned with memory editing in general in this game, since the - // card counts array is encrypted in memory most of the time, and they went - // out of their way to ensure the game uses an area of memory that almost no - // other game uses, which is also used by the Action Replay.) + // The meaning of the high byte is unknown, but it is not used by the decoding function. P is a prime number between + // 1009 (0x3F1) and 2039 (0x7F7), inclusive. V is a 44-bit integer that, when modulated by P, yields the card ID + // (that is, V % P == card_id). When a non-rare card is obtained or lost, the game just increments or decrements the + // value in the card_counts array above, but when a rare card is obtained or lost, the game adds or removes a token + // in rare_tokens and recomputes the count for that card by scanning and decoding all rare tokens. It then writes + // that count to card_counts. This seems to be an anti-cheating measure specifically targeted at memory editing - the + // server could verify that the count in card_counts is correct for rare cards by counting the valid tokens in this + // array. (Sega seemed fairly concerned with memory editing in general in this game, since the card counts array is + // encrypted in memory most of the time, and they went out of their way to ensure the game uses an area of memory + // that almost no other game uses, which is also used by the Action Replay.) /* 05A4:0450 */ parray rare_tokens; /* 13B4:1260 */ parray unknown_a7; /* 1434:12E0 */ parray decks; @@ -915,24 +848,20 @@ struct PlayerConfig { /* 00 */ be_uint32_t guild_card_number; /* 04 */ pstring name; } __packed_ws__(PlayerReference, 0x1C); - // These two arrays are updated when a battle is started (via a 6xB4x05 - // command). The client adds the opposing players' info to ths first two - // entries in recent_human_opponents if the opponents are human. (The - // existing entries are always moved back by two slots, but if one or both - // opponents are not humans, one or both of the newly-vacated slots is not - // filled in.) Both arrays have the most recent entries at the beginning. + // These two arrays are updated when a battle is started (via a 6xB4x05 command). The client adds the opposing + // players' info to ths first two entries in recent_human_opponents if the opponents are human. (The existing entries + // are always moved back by two slots, but if one or both opponents are not humans, one or both of the newly-vacated + // slots is not filled in.) Both arrays have the most recent entries at the beginning. /* 2128:1FD4 */ parray recent_human_opponents; /* 2240:20EC */ parray recent_battle_start_timestamps; /* 2254:2100 */ parray unknown_a10; /* 2268:2114 */ be_uint32_t init_timestamp; /* 226C:2118 */ be_uint32_t last_online_battle_start_timestamp; - // In a certain situation, unknown_t3 is set to init_timestamp plus a multiple - // of two weeks (1209600 seconds). unknown_t3 appears never to be used for - // anything, though. + // In a certain situation, unknown_t3 is set to init_timestamp plus a multiple of two weeks (1209600 seconds). + // unknown_t3 appears never to be used for anything, though. /* 2270:211C */ be_uint32_t unknown_t3; - // This visual config is copied to the player's main visual config when the - // player's name or proportions have changed, or when certain buttons on the - // controller (L, R, X, Y) are held at game start time. + // This visual config is copied to the player's main visual config when the player's name or proportions have + // changed, or when certain buttons on the controller (L, R, X, Y) are held at game start time. /* 2274:2120 */ PlayerVisualConfig backup_visual; /* 22C4:2170 */ parray unknown_a14; /* 2350:21FC */ @@ -998,11 +927,11 @@ enum class AllowedCards : uint8_t { }; struct Rules { - // When this structure is used in a map/quest definition, FF in any of these - // fields means the user is allowed to override it. Any non-FF fields are - // fixed for the map/quest and cannot be overridden. - // The overall time limit is specified in increments of 5 minutes; that is, - // 1 means 5 minutes, 2 means 10 minutes, etc. 0 means no overall time limit. + // When this structure is used in a map/quest definition, FF in any of these fields means the user is allowed to + // override it. Any non-FF fields are fixed for the map/quest and cannot be overridden. + + // The overall time limit is specified in increments of 5 minutes; that is, 1 means 5 minutes, 2 means 10 minutes, + // etc. 0 means no overall time limit. /* 00 */ uint8_t overall_time_limit = 0; /* 01 */ uint8_t phase_time_limit = 0; // In seconds; 0 = unlimited /* 02 */ AllowedCards allowed_cards = AllowedCards::ALL; @@ -1016,21 +945,14 @@ struct Rules { /* 0A */ uint8_t disable_dialogue = 0; // 0 = dialogue on, 1 = dialogue off /* 0B */ DiceExchangeMode dice_exchange_mode = DiceExchangeMode::HIGH_ATK; /* 0C */ uint8_t disable_dice_boost = 0; // 0 = dice boost on, 1 = off - // NOTE: The following fields are unused in PSO's implementation, but newserv - // uses them to implement extended rules. + // NOTE: The following fields are unused in PSO's implementation, but newserv uses them to implement extended rules. /* 0D */ uint8_t def_dice_value_range = 0; // High 4 bits = min, low 4 = max - // These fields specify override dice ranges for the 1-player team in 2v1 + // These fields specify override dice ranges for the 1-player team in 2v1. /* 0E */ uint8_t atk_dice_value_range_2v1 = 0; // High 4 bits = min, low 4 = max /* 0F */ uint8_t def_dice_value_range_2v1 = 0; // High 4 bits = min, low 4 = max /* 10 */ parray unused; /* 14 */ - // Annoyingly, this structure is a different size in Episode 3 Trial Edition. - // This means that many command formats, as well as the map format, are - // different, and the existing Server implementation can't serve Trial Edition - // clients. It'd be nice to support Trial Edition battles, but that would - // likely be more work than it's worth. - Rules() = default; explicit Rules(const phosg::JSON& json); phosg::JSON json() const; @@ -1053,9 +975,8 @@ struct RulesTrial { /* 00 */ uint8_t overall_time_limit = 0; /* 01 */ uint8_t phase_time_limit = 0; /* 02 */ AllowedCards allowed_cards = AllowedCards::ALL; - // In NTE, the dice behave differently than in non-NTE. A zero in either of - // these fields means the corresponding die is random in the range [1, 6]; - // any nonzero value means that die will always take that value. + // In NTE, the dice behave differently than in non-NTE. A zero in either of these fields means the corresponding die + // is random in the range [1, 6]; any nonzero value means that die will always take that value. /* 03 */ uint8_t atk_die_behavior = 0; /* 04 */ uint8_t def_die_behavior = 0; /* 05 */ uint8_t disable_deck_shuffle = 0; @@ -1101,14 +1022,13 @@ struct MapList { be_uint32_t total_size; // Including header, entries, and strings struct Entry { - // The fields in this structure have the same meanings as the corresponding - // fields in MapDefinition. + // The fields in this structure have the same meanings as the corresponding fields in MapDefinition. /* 0000 */ be_uint16_t map_x; /* 0002 */ be_uint16_t map_y; /* 0004 */ be_uint16_t environment_number; /* 0006 */ be_uint16_t map_number; - // Text offsets are from the beginning of the strings block after all map - // entries (that is, add strings_offset to them to get the string offset) + // Text offsets are from the beginning of the strings block after all map entries (that is, add strings_offset to + // them to get the string offset) /* 0008 */ be_uint32_t name_offset; /* 000C */ be_uint32_t location_name_offset; /* 0010 */ be_uint32_t quest_name_offset; @@ -1134,42 +1054,38 @@ struct CompressedMapHeader { // .mnm file format } __packed_ws__(CompressedMapHeader, 8); struct OverlayState { - // In the tiles array, the high 4 bits of each value are the tile type, and - // the low 4 bits are the subtype. The types are: - // 10-1F: blocked by rock (as if the corresponding map_tiles value was 00; - // low 4 bits specify rotation in increments of 1/4 turn) - // 20-2F: blocked by fence (as if the corresponding map_tiles value was 00; - // low 4 bits specify rotation in increments of 1/4 turn) - // 30-34: teleporters (each value should appear exactly twice or not at all): - // 30: purple teleporters - // 31: red teleporters - // 32: green teleporters - // 33: blue teleporters - // 34: purple teleporters (appears the same as 30 but is a distinct set) - // 40-4F: traps on NTE - // 40-44: traps on non-NTE (there may be up to 8 of each type, and one of - // each is chosen to be a real trap at battle start); the trap types are: - // 40: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace - // 41: Gold Rush, Charity, Requiem - // 42: Powerless Rain, Trash 1, Empty Hand, Skip Draw - // 43: Brave Wind, Homesick, Fly - // 44: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix - // 50: blocked by metal box (appears as an improperly-z-buffered teal cube in - // preview; behaves like 10 and 20 in game) + // In the tiles array, the high 4 bits are the tile type, and the low 4 bits are the subtype. The values are: + // 10-1F: blocked by rock (as if the corresponding map_tiles value was 00; low 4 bits specify rotation in + // increments of 1/4 turn) + // 20-2F: blocked by fence (as if the corresponding map_tiles value was 00; low 4 bits specify rotation in + // increments of 1/4 turn) + // 30-34: teleporters (each value should appear exactly twice or not at all): + // 30: purple teleporters + // 31: red teleporters + // 32: green teleporters + // 33: blue teleporters + // 34: purple teleporters (appears the same as 30 but is a distinct set) + // 40-4F: traps on NTE + // 40-44: traps on non-NTE (there may be up to 8 of each type, and one of each is chosen to be a real trap at + // battle start); the trap types are: + // 40: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace + // 41: Gold Rush, Charity, Requiem + // 42: Powerless Rain, Trash 1, Empty Hand, Skip Draw + // 43: Brave Wind, Homesick, Fly + // 44: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix + // 50: blocked by metal box (appears as teal cube in preview; behaves like 10 and 20 in game) // Any other value here will behave like 00 (no special tile behavior). parray, 0x10> tiles; - // This field appears to be unused in both NTE and the final version. Perhaps - // it had some meaning in a pre-NTE version. + // This field appears to be unused in both NTE and the final version. Perhaps it had meaning in a pre-NTE version. parray unused1; // TODO: Figure out exactly where these colors are used parray trap_tile_colors_nte; // Unused on non-NTE - // This specifies the assist card IDs that each trap value (40-4F) will set - // when triggered. This only has an effect on NTE; on non-NTE, this is unused - // and a fixed set of assist cards is used instead. (On newserv, the set of - // used assist cards can be overridden in the server configuration.) + // This specifies the assist card IDs that each trap value (40-4F) will set when triggered. This only has an effect + // on NTE; on non-NTE, this is unused and a fixed set of assist cards is used instead. (On newserv, the set of used + // assist cards can be overridden in the server configuration.) parray trap_card_ids_nte; OverlayState(); @@ -1177,16 +1093,14 @@ struct OverlayState { } __packed_ws__(OverlayState, 0x174); struct MapDefinition { // .mnmd format; also the format of (decompressed) quests - // If tag is not 0x00000100, the game considers the map to be corrupt in - // offline mode and will delete it (if it's a download quest). The tag field - // doesn't seem to have any other use. In Trial Edition, download quests are - // expected to have 0x96 here instead. + // If tag is not 0x00000100, the game considers the map to be corrupt in offline mode and will delete it (if it's a + // download quest). The tag field doesn't seem to have any other use. In Trial Edition, download quests are expected + // to have 0x96 here instead. /* 0000 */ be_uint32_t tag; /* 0004 */ be_uint32_t map_number; // Must be unique across all maps - // The maximum map size is 16 tiles in either dimension, since the various - // tiles arrays below are fixed sizes. + // The maximum map size is 16 tiles in either dimension, since the various tiles arrays below are fixed sizes. /* 0008 */ uint8_t width; /* 0009 */ uint8_t height; @@ -1196,59 +1110,54 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // - The color of the battle tile outlines (probably) // - The preview image to show in the upper-left corner in the map select menu // The environment numbers are: - // 00 = Unguis Lapis ("BONE") - // 01 = Nebula Montana 1 ("ALPINE") - // 02 = Lupus Silva 1 ("FOREST 2-1") - // 03 = Lupus Silva 2 ("FOREST 2-2") - // 04 = Molae Venti ("WINDMILL") - // 05 = Nebula Montana 2 ("ALPINE 2") - // 06 = Tener Sinus ("COAST") - // 07 = Mortis Fons ("GEYSER") - // 08 = Morgue (destroyed) ("BROKEN MORGUE") - // 09 = Tower of Caelum ("TOWER") - // 0A = ??? (crashes) ("MAPMAN") - // 0B = Cyber ("CYBER") - // 0C = Morgue (not destroyed) ("BOSS") - // 0D = (Castor/Pollux map) ("REAL BOSS") - // 0E = Dolor Odor ("STOMACH") - // 0F = Ravum Aedes Sacra ("SACRAMENT") - // 10 = (Amplum Umbra map) ("RUIN") - // 11 = Via Tubus ("METRO") - // 12 = Morgue ("NORMAL MORGUE") - // Environment numbers above 12 are replaced with 0B (Cyber) if specified in - // a map definition. The following environment numbers are used internally by - // the game for various functions, but cannot be specified in MapDefinitions: - // 13 = TCardDemo (sends CAx14 and CAx13, then softlocks at black screen) - // 14 = crashes - // 15 = crashes - // 16 = Battle results screen - // 17 = Game Over screen (if used online, client disconnects without saving) - // 18 = Episode 3 staff roll - // 19 = View Battle waiting room - // 1A = TCard00_Select (debug battle setup menu) - // 1B = nothing (softlocks at black screen) + // 00 = Unguis Lapis ("BONE") + // 01 = Nebula Montana 1 ("ALPINE") + // 02 = Lupus Silva 1 ("FOREST 2-1") + // 03 = Lupus Silva 2 ("FOREST 2-2") + // 04 = Molae Venti ("WINDMILL") + // 05 = Nebula Montana 2 ("ALPINE 2") + // 06 = Tener Sinus ("COAST") + // 07 = Mortis Fons ("GEYSER") + // 08 = Morgue (destroyed) ("BROKEN MORGUE") + // 09 = Tower of Caelum ("TOWER") + // 0A = ??? (crashes) ("MAPMAN") + // 0B = Cyber ("CYBER") + // 0C = Morgue (not destroyed) ("BOSS") + // 0D = (Castor/Pollux map) ("REAL BOSS") + // 0E = Dolor Odor ("STOMACH") + // 0F = Ravum Aedes Sacra ("SACRAMENT") + // 10 = (Amplum Umbra map) ("RUIN") + // 11 = Via Tubus ("METRO") + // 12 = Morgue ("NORMAL MORGUE") + // Environment numbers above 12 are replaced with 0B (Cyber) if specified in a map definition. The following + // environment numbers are used internally by the game for various functions, but cannot be specified in maps: + // 13 = TCardDemo (sends CAx14 and CAx13, then softlocks at black screen) + // 14 = crashes + // 15 = crashes + // 16 = Battle results screen + // 17 = Game Over screen (if used online, client disconnects without saving) + // 18 = Episode 3 staff roll + // 19 = View Battle waiting room + // 1A = TCard00_Select (debug battle setup menu) + // 1B = nothing (softlocks at black screen) /* 000A */ uint8_t environment_number; // This field specifies how many of the camera_zone_maps are used. /* 000B */ uint8_t num_camera_zones; // In the map_tiles array, the values are usually: - // 00 = not a valid tile (blocked) - // 01 = valid tile unless modified out (via modification_tiles) - // 02 = team A start (1v1) - // 03, 04 = team A start (2v2) - // 06, 07 = team B start (2v2) - // 08 = team B start (1v1) - // These values can be redefined by start_tile_definitions below, however. - // Note that the game displays the map reversed vertically in the preview - // window. For example, player 1 is on team A, which usually starts at the top - // of the map as defined in this struct, or at the bottom as shown in the - // preview window. + // 00 = not a valid tile (blocked) + // 01 = valid tile unless modified out (via modification_tiles) + // 02 = team A start (1v1) (can be redefined by start_tile_definitions) + // 03, 04 = team A start (2v2) (can be redefined by start_tile_definitions) + // 06, 07 = team B start (2v2) (can be redefined by start_tile_definitions) + // 08 = team B start (1v1) (can be redefined by start_tile_definitions) + // Note that the game displays the map reversed vertically in the preview window. For example, player 1 is on team A, + // which usually starts at the top of the map as defined in this struct, or at the bottom as shown in the preview. /* 000C */ parray, 0x10> map_tiles; - // The start_tile_definitions field is a list of 6 bytes for each team. The - // low 6 bits of each byte match the starting location for the relevant player - // in map_tiles; the high 2 bits are the player's initial facing direction. + // The start_tile_definitions field is a list of 6 bytes for each team. The low 6 bits of each byte match the + // starting location for the relevant player in map_tiles; the high 2 bits are the player's initial facing direction. // - If the team has 1 player, only byte [0] is used. // - If the team has 2 players, bytes [1] and [2] are used. // - If the team has 3 players, bytes [3] through [5] are used. @@ -1259,11 +1168,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 24 */ be_float camera_x; /* 28 */ be_float camera_y; /* 2C */ be_float camera_z; - // It appears that the camera always aligns its +Y raster axis with +Y in - // the virtual world. If the focus point is directly beneath the camera - // point, the logic for deciding which direction should be "up" from the - // camera's perspective can get confused and jitter back and forth as the - // camera moves into position. + // It appears that the camera always aligns its +Y raster axis with +Y in the virtual world. If the focus point is + // directly beneath the camera point, the logic for deciding which direction should be "up" from the camera's + // perspective can get confused and jitter back and forth as the camera moves into position. /* 30 */ be_float focus_x; /* 34 */ be_float focus_y; /* 38 */ be_float focus_z; @@ -1274,29 +1181,22 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests phosg::JSON json() const; } __packed_ws__(CameraSpec, 0x48); - // This array specifies the camera zone maps. A camera zone map is a subset of - // the main map (specified in map_tiles). Tiles that are part of each camera - // zone are 1 in these arrays; all other tiles are 0. The game evaluates each - // camera zone in order; if all SCs and FCs are within a particular camera - // zone, then the corresponding camera location is used as the default camera - // location. If the player doesn't move the camera with the C stick, then the - // camera zones are evaluated continuously during the battle, and the camera - // will move to focus on the part of the field where the SCs/FCs are. (Or, - // more accurately, where the corresponding entry in camera_zone_specs says - // to focus.) camera_zone_maps is indexed as [team_id][camera_zone_num][y][x]; - // camera_zone_specs is indexed as [team_id][camera_zone_num]. Unused entries - // (beyond num_camera_zones) in both arrays should be filled with FF bytes. + // This array specifies the camera zone maps. A camera zone map is a subset of the main map (specified in map_tiles). + // Tiles that are part of each camera zone are 1 in these arrays; all other tiles are 0. The game evaluates each + // camera zone in order; if all SCs and FCs are within a particular camera zone, then the corresponding camera + // location is used as the default camera location. If the player doesn't move the camera with the C stick, then the + // camera zones are evaluated continuously during the battle, and the camera will move to focus on the part of the + // field where the SCs/FCs are. (Or, more accurately, where the corresponding entry in camera_zone_specs says to + // focus.) camera_zone_maps is indexed as [team_id][camera_zone_num][y][x]; camera_zone_specs is indexed as + // [team_id][camera_zone_num]. Unused entries (beyond num_camera_zones) in both arrays should be filled with 0xFF. /* 0118 */ parray, 0x10>, 10>, 2> camera_zone_maps; /* 1518 */ parray, 2> camera_zone_specs; - // These camera specs are used in the Move phase, when the player has chosen - // an SC or FC to move, or when the player presses Start/Z. Normally these are - // defined such that the camera is placed high above the map, giving an - // overhead view of the entire playfield. This is indexed as [???][team_id] - // (it is not yet known what the major index represents). + // These camera specs are used in the Move phase, when the player has chosen an SC or FC to move, or when the player + // presses Start/Z. Normally these are defined such that the camera is placed high above the map, giving an overhead + // view of the entire playfield. This is indexed as [???][team_id] (it is not yet known what the major index is). /* 1AB8 */ parray, 3> overview_specs; - // This specifies the locations of blocked tiles, teleporters, and traps. See - // the comments in OverlayState for details. + // This specifies the locations of blocked tiles, teleporters, and traps. See comments in OverlayState for details. /* 1C68 */ OverlayState overlay_state; /* 1DDC */ Rules default_rules; @@ -1306,8 +1206,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 1E18 */ pstring quest_name; // == location_name if not a quest /* 1E54 */ pstring description; - // These fields describe where the map cursor on the preview screen should - // scroll to + // These fields describe where the map cursor on the preview screen should scroll to. /* 1FE4 */ be_uint16_t map_x; /* 1FE6 */ be_uint16_t map_y; @@ -1319,10 +1218,8 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests } __packed_ws__(NPCDeck, 0x58); /* 1FE8 */ parray npc_decks; // Unused if name[0] == 0 - // These are almost (but not quite) the same format as the entries in - // aiprm.dat. These entries are only used if the corresponding NPC exists - // (if .name[0] is not 0) and if the corresponding entry in the - // npc_ai_params_entry_index is -1. + // These are not quite the same format as the entries in aiprm.dat. These entries are only used if the corresponding + // NPC exists (if .name[0] is not 0) and if the corresponding entry in the npc_ai_params_entry_index is -1. struct AIParams { /* 0000 */ parray unknown_a1; /* 0004 */ uint8_t is_arkz; @@ -1337,140 +1234,126 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 242C */ parray unknown_a7; - // This array specifies which set of predefined AI parameters (in aiprm.dat) - // to use for each NPC. If a value in this array is -1 (FFFFFFFF), then the - // corresponding NPC's AI parameters are defined in the AIParams structure - // above. The names of the AI parameter sets defined in aiprm.dat are: - // 00 => Sample_Hunter 0D => Sample_Dark 1A => LKnight - // 01 => Glustar 0E => Break 1B => Boss_Castor - // 02 => Guykild 0F => Creinu 1C => Boss_Pollux - // 03 => Inolis 10 => Endu 1D => Sample_Dark - // 04 => Kilia 11 => Heiz - // 05 => Kranz 12 => KC - // 06 => Orland 13 => Lura - // 07 => Relmitos 14 => memoru - // 08 => Saligun 15 => Ohgun - // 09 => Silfer 16 => Peko - // 0A => Sample_Hunter 17 => Reiz - // 0B => Teifu 18 => Rio - // 0C => Viviana 19 => Rufina - // Presumably 0A is meant to be Stella and 1D is meant to be Amplum Umbra, but - // they forgot to change the names. + // This array specifies which set of predefined AI parameters (in aiprm.dat) to use for each NPC. If a value in this + // array is -1 (FFFFFFFF), then the corresponding NPC's AI parameters are defined in the AIParams structure above. + // The names of the AI parameter sets defined in aiprm.dat are: + // 00 => Sample_Hunter 0D => Sample_Dark 1A => LKnight + // 01 => Glustar 0E => Break 1B => Boss_Castor + // 02 => Guykild 0F => Creinu 1C => Boss_Pollux + // 03 => Inolis 10 => Endu 1D => Sample_Dark + // 04 => Kilia 11 => Heiz + // 05 => Kranz 12 => KC + // 06 => Orland 13 => Lura + // 07 => Relmitos 14 => memoru + // 08 => Saligun 15 => Ohgun + // 09 => Silfer 16 => Peko + // 0A => Sample_Hunter 17 => Reiz + // 0B => Teifu 18 => Rio + // 0C => Viviana 19 => Rufina + // Presumably 0A is meant to be Stella and 1D is meant to be Amplum Umbra, but they forgot to change the names. /* 2434 */ parray npc_ai_params_entry_index; - // before_message appears before the battle if it's not blank. after_message - // appears after the battle if it's not blank. dispatch_message appears right - // before the player chooses a deck if it's not blank; usually it says + // before_message appears before the battle if it's not blank. after_message appears after the battle if it's not + // blank. dispatch_message appears right before the player chooses a deck if it's not blank; usually it says // something like "You can only dispatch ". /* 2440 */ pstring before_message; /* 25D0 */ pstring after_message; /* 2760 */ pstring dispatch_message; struct DialogueSet { - // Dialogue sets specify lines that COMs can say at certain points during - // the battle. They only apply to COMs which are defined as NPCs in the map - // definition; human players and COMs chosen by humans never say lines from - // dialogue sets. + // Dialogue sets specify lines that COMs can say at certain points during the battle. They only apply to COMs which + // are defined as NPCs in the map definition; human players and COMs chosen by humans never say these lines. - // when specifies the situation in which this dialogue set activates. The - // values 0000-000C (inclusive) can be used here, or FFFF if the entire - // dialogue set is unused. The known values are: + // when specifies the situation in which this dialogue set activates. The values 0000-000C (inclusive) can be used + // here, or FFFF if the entire dialogue set is unused. The known values are: // 0001: Activates at battle start if player is an opponent // 0006: Activates when below 50% HP // 0008: Activates at battle start if player is an ally /* 0000 */ be_int16_t when; // 0x00-0x0C, or FFFF if unused /* 0002 */ be_uint16_t percent_chance; // 0-100, or FFFF if unused - // If the dialogue set activates, the game randomly chooses one of these - // strings, excluding any that are empty or begin with the character '^'. + // If the dialogue set activates, the game randomly chooses one of these strings, excluding any that are empty or + // begin with the character '^'. /* 0004 */ parray, 4> strings; /* 0104 */ phosg::JSON json(Language language) const; } __packed_ws__(DialogueSet, 0x104); - // There are up to 0x10 of these per valid NPC, but only the first 13 of them - // are used, since each one must have a unique value for .when and the values - // there can only be 0-12. + // There are up to 0x10 of these per valid NPC, but only the first 13 of them are used, since each one must have a + // unique value for .when and the values there can only be 0-12. /* 28F0 */ parray, 3> dialogue_sets; - // These card IDs are always given to the player when they win a battle on - // this map. Unused entries should be set to FFFF. Cards in this array are - // ignored if they have any of these features (in the card definition): + // These card IDs are always given to the player when they win a battle on this map. Unused entries should be set to + // FFFF. Cards in this array are ignored if they have any of these features (in the card definition): // - type is HUNTERS_SC or ARKZ_SC // - card_class is BOSS_ATTACK_ACTION or BOSS_TECH // - rank is D1, D2, or D3 - // - cannot_drop is 1 (specifically 1; other values don't prevent cards from - // appearing) + // - cannot_drop is 1 (specifically 1; other values don't prevent cards from appearing) /* 59B0 */ parray reward_card_ids; - // These fields are used when determining which cards to drop after the battle - // is complete. If either is negative, the player's CLv is used instead. + // These fields are used when determining which cards to drop after the battle is complete. If either is negative, + // the player's CLv is used instead. /* 59D0 */ be_int32_t win_level_override; /* 59D4 */ be_int32_t loss_level_override; - // The field offsets specify where the battlefield should appear relative to - // the center of the environment. The size of one tile on the field is 25 - // units in these fields. + // The field offsets specify where the battlefield should appear relative to the center of the environment. The size + // of one tile on the field is 25 units in these fields. /* 59D8 */ be_int16_t field_offset_x; /* 59DA */ be_int16_t field_offset_y; - // map_category specifies where the map should appear in the maps menu. If - // this is 0, 1, or 2, the map appears in the Quest section; otherwise, it - // appears in the Free Battle section instead. It's not known if this controls - // anything else, or what the difference is in behavior between 0, 1, and 2. + // map_category specifies where the map should appear in the maps menu. If this is 0, 1, or 2, the map appears in the + // Quest section; otherwise, it appears in the Free Battle section instead. It's not known if this controls anything + // else, or what the difference is in behavior between 0, 1, and 2. /* 59DC */ uint8_t map_category; - // This field determines block graphics to be used in the Cyber environment. - // There are 10 block types (0-9); if this value is > 9, type 0 is used. This - // field has no effect in Ep3 NTE, even though there are 6 different block - // texture files on the NTE disc. + // This field determines block graphics to be used in the Cyber environment. There are 10 block types (0-9); if this + // value is > 9, type 0 is used. This field has no effect in Ep3 NTE, even though there are 6 different block texture + // files on the NTE disc. /* 59DD */ uint8_t cyber_block_type; /* 59DE */ be_uint16_t unknown_a11; - // This array specifies which SC characters can't participate in the quest - // (that is, the player is not allowed to choose decks with these SC cards). - // The values in this array don't match the SC card IDs, however: + // This array specifies which SC characters can't participate in the quest (that is, the player is not allowed to + // choose decks with these SC cards). The values in this array don't match the SC card IDs, however: // value in array => SC name (SC card ID) - // 0000 => Guykild (0005) 000C => Hyze (0117) - // 0001 => Kylria (0006) 000D => Rufina (0118) - // 0002 => Saligun (0110) 000E => Peko (0119) - // 0003 => Relmitos (0111) 000F => Creinu (011A) - // 0004 => Kranz (0002) 0010 => Reiz (011B) - // 0005 => Sil'fer (0004) 0011 => Lura (0007) - // 0006 => Ino'lis (0003) 0012 => Break (0008) - // 0007 => Viviana (0112) 0013 => Rio (011C) - // 0008 => Teifu (0113) 0014 => Endu (0116) - // 0009 => Orland (0001) 0015 => Memoru (011D) - // 000A => Stella (0114) 0016 => K.C. (011E) - // 000B => Glustar (0115) 0017 => Ohgun (011F) - // These values normally can't be used by the player, but are recognized - // internally by the game: - // 0018 => HERO_1 (02AA) 0021 => DARK_4 (02B3) - // 0019 => HERO_2 (02AB) 0022 => DARK_5 (02B4) - // 001A => HERO_3 (02AC) 0023 => DARK_6 (02B5) - // 001B => HERO_4 (02AD) 0024 => LEUKON (029B) - // 001C => HERO_5 (02AE) 0025 => CASTOR (029C) - // 001D => HERO_6 (02AF) 0026 => POLLUX (029D) - // 001E => DARK_1 (02B0) 0027 => AMPLUM (029E) - // 001F => DARK_2 (02B1) 0028 => CASTOR_USR (02BE) - // 0020 => DARK_3 (02B2) 0029 => POLLUX_USR (02BF) + // 0000 => Guykild (0005) 000C => Hyze (0117) + // 0001 => Kylria (0006) 000D => Rufina (0118) + // 0002 => Saligun (0110) 000E => Peko (0119) + // 0003 => Relmitos (0111) 000F => Creinu (011A) + // 0004 => Kranz (0002) 0010 => Reiz (011B) + // 0005 => Sil'fer (0004) 0011 => Lura (0007) + // 0006 => Ino'lis (0003) 0012 => Break (0008) + // 0007 => Viviana (0112) 0013 => Rio (011C) + // 0008 => Teifu (0113) 0014 => Endu (0116) + // 0009 => Orland (0001) 0015 => Memoru (011D) + // 000A => Stella (0114) 0016 => K.C. (011E) + // 000B => Glustar (0115) 0017 => Ohgun (011F) + // These values normally can't be used by the player, but are recognized internally by the game: + // 0018 => HERO_1 (02AA) 0021 => DARK_4 (02B3) + // 0019 => HERO_2 (02AB) 0022 => DARK_5 (02B4) + // 001A => HERO_3 (02AC) 0023 => DARK_6 (02B5) + // 001B => HERO_4 (02AD) 0024 => LEUKON (029B) + // 001C => HERO_5 (02AE) 0025 => CASTOR (029C) + // 001D => HERO_6 (02AF) 0026 => POLLUX (029D) + // 001E => DARK_1 (02B0) 0027 => AMPLUM (029E) + // 001F => DARK_2 (02B1) 0028 => CASTOR_USR (02BE) + // 0020 => DARK_3 (02B2) 0029 => POLLUX_USR (02BF) // Unused entries in this array should be set to FFFF. /* 59E0 */ parray unavailable_sc_cards; // This array specifies which restrictions apply to each player slot. struct EntryState { // Values for player_type: - // 00 = Player (selectable by player, COM decks not allowed) - // 01 = Player/COM (selectable by player, player and COM decks allowed) - // 02 = COM (selectable by player, player decks not allowed) - // 03 = COM (not selectable by player; uses an NPC deck defined above) - // 04 = NONE (not selectable by player) - // FF = FREE (same as Player/COM, used in free battle mode) + // 00 = Player (selectable by player, COM decks not allowed) + // 01 = Player/COM (selectable by player, player and COM decks allowed) + // 02 = COM (selectable by player, player decks not allowed) + // 03 = COM (not selectable by player; uses an NPC deck defined above) + // 04 = NONE (not selectable by player) + // FF = FREE (same as Player/COM, used in free battle mode) uint8_t player_type; // Values for deck_type: - // 00 = HERO ONLY - // 01 = DARK ONLY - // FF = any deck allowed + // 00 = HERO ONLY + // 01 = DARK ONLY + // FF = any deck allowed uint8_t deck_type; bool operator==(const EntryState& other) const = default; @@ -1484,9 +1367,8 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests return (this->map_category <= 2); } - // This function throws runtime_error if the passed-in map is not semantically - // equivalent to *this. Semantic equivalence means all fields that affect - // gameplay and visuals are equivalent, but dialogue, names, and description + // This function throws runtime_error if the passed-in map is not semantically equivalent to *this. Semantic + // equivalence means all fields that affect gameplay and visuals are equivalent, but dialogue, names, and description // text may differ. void assert_semantically_equivalent(const MapDefinition& other) const; @@ -1495,8 +1377,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests } __packed_ws__(MapDefinition, 0x5A18); struct MapDefinitionTrial { - // This is the format of Episode 3 Trial Edition maps. See the comments in - // MapDefinition for what each field means. + // This is the format of Episode 3 Trial Edition maps. See the comments in MapDefinition for what each field means. /* 0000 */ be_uint32_t tag; /* 0004 */ be_uint32_t map_number; @@ -1533,8 +1414,8 @@ struct MapDefinitionTrial { /* 4174 */ uint8_t map_category; /* 4175 */ uint8_t cyber_block_type; /* 4176 */ be_uint16_t unknown_a11; - // TODO: This field may contain some version of unavailable_sc_cards and/or - // entry_states from MapDefinition, but the format isn't the same + // TODO: This field may contain some version of unavailable_sc_cards and/or entry_states from MapDefinition, but the + // format isn't the same /* 4178 */ parray unknown_t12; /* 41A0 */ @@ -1681,8 +1562,7 @@ public: } private: - // The compressed map lists are generated on demand from the maps map below. - // THey are indexed as [language][num_players] + // The compressed map lists are generated on demand; these are indexed as [language][num_players] mutable std::vector> compressed_map_lists_trial; mutable std::vector> compressed_map_lists_final; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index a5833f2a..3f3e3d0b 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -3356,7 +3356,7 @@ void send_rare_enemy_index_list(shared_ptr c, const vector& inde } void send_quest_function_call(std::shared_ptr ch, uint16_t label) { - S_CallQuestFunction_V3_BB_AB cmd; + S_CallQuestLabel_V3_BB_AB cmd; cmd.label = label; ch->send(0xAB, 0x00, &cmd, sizeof(cmd)); }