add some undocumented client commands from PC, GC and BB
This commit is contained in:
+382
-79
@@ -277,6 +277,20 @@ struct S_ServerInit_DC_PC_GC_02_17_92_9B {
|
||||
ptext<char, 0xC0> after_message;
|
||||
};
|
||||
|
||||
// 03 (C->S): Legacy login (non-BB)
|
||||
|
||||
struct C_LegacyLogin_PC_GC_03 {
|
||||
le_uint64_t unused; // Same as unused field in 9D/9E
|
||||
le_uint32_t sub_version;
|
||||
parray<uint8_t, 4> unused2; // Same as the first 4 bytes of 9D/9E's unused2
|
||||
// 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.)
|
||||
ptext<char, 0x10> serial_number2;
|
||||
ptext<char, 0x10> access_key2;
|
||||
};
|
||||
|
||||
// 03 (S->C): Legacy password check result (non-BB)
|
||||
// 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
|
||||
@@ -303,14 +317,20 @@ struct S_ServerInit_BB_03 {
|
||||
// See comments on non-BB 03 (S->C). This is likely a relic of an older,
|
||||
// now-unused sequence.
|
||||
// header.flag is nonzero, but it's not clear what it's used for.
|
||||
struct C_LegacyLogin_GC_04 {
|
||||
uint32_t unknown_a1[2];
|
||||
struct C_LegacyLogin_PC_GC_04 {
|
||||
le_uint64_t unused1; // Same as unused field in 9D/9E
|
||||
le_uint32_t sub_version;
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t unused2;
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
};
|
||||
|
||||
struct C_LegacyLogin_BB_04 {
|
||||
parray<le_uint32_t, 3> unknown_a1;
|
||||
ptext<char, 0x10> username;
|
||||
ptext<char, 0x10> password;
|
||||
};
|
||||
|
||||
// 04 (S->C): Set guild card number and update client config ("security data")
|
||||
// The client config field in this command is only used by V3 clients (PSO GC).
|
||||
// We send it anyway to clients on earlier versions (e.g. PSO PC), but they
|
||||
@@ -427,17 +447,44 @@ struct S_Unknown_GC_0E {
|
||||
// 0F: Invalid command
|
||||
|
||||
// 10 (C->S): Menu selection
|
||||
// header.flag contains two flags: 02 specifies if a password is present, and 01
|
||||
// specifies... something else. 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.
|
||||
|
||||
struct C_MenuSelection {
|
||||
struct C_MenuSelection_10_Flag00 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
// Password is only present when client attempts to join a locked game.
|
||||
union {
|
||||
char dcgc[0];
|
||||
char16_t pcbb[0];
|
||||
} password;
|
||||
};
|
||||
|
||||
template <typename CharT>
|
||||
struct C_MenuSelection_10_Flag01 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
ptext<CharT, 0x10> unknown_a1;
|
||||
};
|
||||
struct C_MenuSelection_DC_GC_10_Flag01 : C_MenuSelection_10_Flag01<char> { };
|
||||
struct C_MenuSelection_PC_BB_10_Flag01 : C_MenuSelection_10_Flag01<char16_t> { };
|
||||
|
||||
template <typename CharT>
|
||||
struct C_MenuSelection_10_Flag02 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
ptext<CharT, 0x10> password;
|
||||
};
|
||||
struct C_MenuSelection_DC_GC_10_Flag02 : C_MenuSelection_10_Flag02<char> { };
|
||||
struct C_MenuSelection_PC_BB_10_Flag02 : C_MenuSelection_10_Flag02<char16_t> { };
|
||||
|
||||
template <typename CharT>
|
||||
struct C_MenuSelection_10_Flag03 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
ptext<CharT, 0x10> unknown_a1;
|
||||
ptext<CharT, 0x10> password;
|
||||
};
|
||||
struct C_MenuSelection_DC_GC_10_Flag03 : C_MenuSelection_10_Flag03<char> { };
|
||||
struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03<char16_t> { };
|
||||
|
||||
// 11 (S->C): Ship info
|
||||
// Same format as 01 command.
|
||||
|
||||
@@ -527,6 +574,11 @@ struct S_ReconnectSplit_19 {
|
||||
|
||||
// 1E: Invalid command
|
||||
|
||||
// 1F (C->S): Request information menu
|
||||
// No arguments
|
||||
// This command is used in PSO PC. It exists in PSO GC as well but is apparently
|
||||
// unused.
|
||||
|
||||
// 1F (S->C): Information menu
|
||||
// Same format and usage as 07 command, except:
|
||||
// - The menu title will say "Information" instead of "Ship Select".
|
||||
@@ -542,13 +594,16 @@ struct S_ReconnectSplit_19 {
|
||||
// Format unknown
|
||||
|
||||
// 22: GameGuard check (BB)
|
||||
// An older version of BB used a 4-byte challenge in the flag field (and the
|
||||
// command had no payload). The latest version uses this 16-byte challenge.
|
||||
|
||||
struct SC_GameCardCheck_BB_22 {
|
||||
parray<uint8_t, 0x10> data;
|
||||
// Command 0022 is a 16-byte challenge (sent in the data field) using the
|
||||
// following structure.
|
||||
|
||||
struct SC_GameCardCheck_BB_0022 {
|
||||
parray<le_uint32_t, 4> data;
|
||||
};
|
||||
|
||||
// Command 0122 uses a 4-byte challenge sent in the header.flag field instead.
|
||||
|
||||
// 23 (S->C): Unknown (BB)
|
||||
// header.flag is used, but command has no other arguments.
|
||||
|
||||
@@ -701,7 +756,8 @@ struct C_OpenFileConfirmation_44_A6 {
|
||||
|
||||
// 61 (C->S): Player data
|
||||
// See PSOPlayerDataPC, PSOPlayerDataGC, and PSOPlayerDataBB in Player.hh for
|
||||
// this command's format.
|
||||
// this command's format. header.flag appears to be the game's major version:
|
||||
// it's 02 on PSO PC, 03 on PSO GC 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
|
||||
@@ -966,7 +1022,8 @@ struct S_ArrowUpdateEntry_88 {
|
||||
|
||||
struct C_LegacyLogin_GC_90 {
|
||||
ptext<char, 0x11> serial_number;
|
||||
ptext<char, 0x13> access_key;
|
||||
ptext<char, 0x11> access_key;
|
||||
parray<uint8_t, 2> unused;
|
||||
};
|
||||
|
||||
// 90 (S->C): License verification result (GC)
|
||||
@@ -1024,23 +1081,32 @@ struct C_Login_BB_93 {
|
||||
// servers may have just duplicated Sega's behavior verbatim.
|
||||
// Client will respond with a 61 command.
|
||||
|
||||
// 96 (C->S): Client checksum
|
||||
// 96 (C->S): Character save information
|
||||
|
||||
struct C_ClientChecksum_GC_96 {
|
||||
// It seems only at most 48 bits of this are actually used - the highest-order
|
||||
// (last) two bytes seem to always be zero.
|
||||
le_uint64_t checksum;
|
||||
struct C_CharSaveInfo_GC_BB_96 {
|
||||
// This field appears to be a checksum or random stamp of some sort; it seems
|
||||
// to be unique and constant per character.
|
||||
le_uint32_t unknown_a1;
|
||||
// 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;
|
||||
};
|
||||
|
||||
// 97 (S->C): Save to memory card
|
||||
// No arguments
|
||||
// According to Sylverant's documentation, sending this command with header.flag
|
||||
// == 0 will cause the client to delete a lot of the character's data. This was
|
||||
// presumably intended to be used by Sega if they detected cheating. newserv
|
||||
// always sends header.flag = 1 here, which saves without deleting anything.
|
||||
// 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.
|
||||
// Client will respond with a B1 command if header.flag is nonzero.
|
||||
|
||||
// 98 (C->S): Leave game
|
||||
// Same format as 61 command.
|
||||
// Same format as 61 command. header.flag appears to be the major game version;
|
||||
// it's 03 on PSO GC Episodes 1 & 2 (and BB) and 04 on Episode 3.
|
||||
// Client will send an 84 when it's ready to join a lobby.
|
||||
|
||||
// 99 (C->S): Server time accepted
|
||||
@@ -1049,12 +1115,13 @@ struct C_ClientChecksum_GC_96 {
|
||||
// 9A (C->S): Initial login (no password or client config)
|
||||
|
||||
struct C_Login_DC_PC_GC_9A {
|
||||
ptext<char, 0x20> unused;
|
||||
ptext<char, 0x10> unused1;
|
||||
ptext<char, 0x10> unused2;
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
uint32_t player_tag;
|
||||
uint32_t guild_card_number;
|
||||
uint32_t sub_version;
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card_number;
|
||||
le_uint32_t sub_version;
|
||||
ptext<char, 0x30> serial_number2;
|
||||
ptext<char, 0x30> access_key2;
|
||||
ptext<char, 0x30> email_address;
|
||||
@@ -1093,9 +1160,10 @@ struct C_Login_DC_PC_GC_9A {
|
||||
// - The client will respond with a command DB instead of a command 93.
|
||||
|
||||
// 9C (C->S): Register
|
||||
// It appears PSO GC sends uninitialized data in the header.flag field here.
|
||||
|
||||
struct C_Register_DC_PC_GC_9C {
|
||||
ptext<char, 8> unused;
|
||||
le_uint64_t unused;
|
||||
le_uint32_t sub_version;
|
||||
le_uint32_t unused2;
|
||||
ptext<char, 0x30> serial_number;
|
||||
@@ -1103,6 +1171,14 @@ struct C_Register_DC_PC_GC_9C {
|
||||
ptext<char, 0x30> password;
|
||||
};
|
||||
|
||||
struct C_Register_BB_9C {
|
||||
le_uint32_t sub_version;
|
||||
le_uint32_t unknown_a1; // Only the second byte (0x0000??00) is used, but is 0
|
||||
ptext<char, 0x30> username;
|
||||
ptext<char, 0x30> password;
|
||||
ptext<char, 0x30> game_tag; // "psopc2" on BB
|
||||
};
|
||||
|
||||
// 9C (S->C): Register result
|
||||
// The only possible error here seems to be wrong password (127) which is
|
||||
// displayed if the header.flag field is zero. If header.flag is nonzero, the
|
||||
@@ -1119,15 +1195,19 @@ struct C_Login_PC_9D {
|
||||
le_uint32_t guild_card_number; // 0xFFFFFFFF if not set
|
||||
le_uint64_t unused;
|
||||
le_uint32_t sub_version;
|
||||
parray<uint8_t, 0x24> unused2; // 00 01 00 00 ... (rest is 00)
|
||||
uint8_t is_extended; // If 1, structure has extended format
|
||||
uint8_t unknown_a1; // Always 1?
|
||||
parray<uint8_t, 0x2> unused3; // Always zeroes?
|
||||
ptext<char, 0x10> unused1; // Same as unused1/unused2 in 9A
|
||||
ptext<char, 0x10> unused2;
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
ptext<char, 0x30> serial_number2;
|
||||
ptext<char, 0x30> access_key2;
|
||||
ptext<char, 0x10> name;
|
||||
};
|
||||
struct C_LoginWithUnusedSpace_PC_9D : C_Login_PC_9D {
|
||||
parray<uint8_t, 0x84> unused_space;
|
||||
struct C_LoginExtended_PC_9D : C_Login_PC_9D {
|
||||
parray<uint8_t, 0x84> unknown_a2;
|
||||
};
|
||||
|
||||
// 9E (C->S): Log in with client config
|
||||
@@ -1143,8 +1223,25 @@ struct C_Login_GC_9E : C_Login_PC_9D {
|
||||
ClientConfigFields() : data() { }
|
||||
} client_config;
|
||||
};
|
||||
struct C_LoginWithUnusedSpace_GC_9E : C_Login_GC_9E {
|
||||
parray<uint8_t, 0x64> unused_space;
|
||||
struct C_LoginExtended_GC_9E : C_Login_GC_9E {
|
||||
parray<uint8_t, 0x64> unknown_a2;
|
||||
};
|
||||
|
||||
struct C_LoginExtended_BB_9E {
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card_number; // == serial_number when on newserv
|
||||
le_uint32_t sub_version;
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
ptext<char, 0x10> unknown_a3; // Always blank?
|
||||
ptext<char, 0x10> unknown_a4; // == "?"
|
||||
ptext<char, 0x10> unknown_a5; // Always blank?
|
||||
ptext<char, 0x10> unknown_a6; // Always blank?
|
||||
ptext<char, 0x30> username;
|
||||
ptext<char, 0x30> password;
|
||||
ptext<char, 0x10> guild_card_number_str;
|
||||
parray<le_uint32_t, 10> unknown_a7;
|
||||
parray<uint8_t, 0x84> unused; // Always zero
|
||||
};
|
||||
|
||||
// 9F (S->C): Request client config / security data
|
||||
@@ -1159,7 +1256,7 @@ struct C_LoginWithUnusedSpace_GC_9E : C_Login_GC_9E {
|
||||
struct C_ChangeShipOrBlock_A0_A1 {
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card_number;
|
||||
parray<uint8_t, 0x10> unknown; // all zeroes
|
||||
parray<uint8_t, 0x10> unused; // Verified as unused from client disassembly
|
||||
};
|
||||
|
||||
// A0 (S->C): Ship select menu
|
||||
@@ -1225,6 +1322,7 @@ struct S_QuestMenuEntry_BB_A2_A4 {
|
||||
// This command is sent when the in-game quest menu (A2) is closed. 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 the flag argument.
|
||||
|
||||
// AA (C->S): Update quest statistics
|
||||
// This command is used in Maximum Attack 2, but its format is unlikely to be
|
||||
@@ -1233,13 +1331,14 @@ struct S_QuestMenuEntry_BB_A2_A4 {
|
||||
// The server will respond with an AB command.
|
||||
|
||||
struct C_UpdateQuestStatistics_AA {
|
||||
le_uint32_t quest_internal_id;
|
||||
le_uint16_t quest_internal_id;
|
||||
le_uint16_t unused;
|
||||
le_uint16_t request_token;
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint32_t kill_count;
|
||||
le_uint32_t time_taken; // in seconds
|
||||
parray<uint8_t, 0x14> unknown_a3;
|
||||
parray<le_uint32_t, 5> unknown_a3;
|
||||
};
|
||||
|
||||
// AB (S->C): Confirm update quest statistics
|
||||
@@ -1356,9 +1455,9 @@ struct S_RankUpdate_GC_Ep3_B7 {
|
||||
le_uint32_t jukebox_songs_unlocked;
|
||||
};
|
||||
|
||||
// B8 (C->S): Confirm rank update
|
||||
// B7 (C->S): Confirm rank update
|
||||
// No arguments
|
||||
// The client sends this after it receives a B8 from the server.
|
||||
// The client sends this after it receives a B7 from the server.
|
||||
|
||||
// B8 (S->C): Update card definitions (Episode 3)
|
||||
// Contents is a single little-endian le_uint32_t specifying the size of the
|
||||
@@ -1450,15 +1549,15 @@ struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry<char16_t> { };
|
||||
|
||||
template <typename CharT>
|
||||
struct C_CreateGame {
|
||||
le_uint64_t unused;
|
||||
parray<le_uint32_t, 2> unused;
|
||||
ptext<CharT, 0x10> name;
|
||||
ptext<CharT, 0x10> password;
|
||||
uint8_t difficulty; // 0-3
|
||||
uint8_t battle_mode; // 0 or 1
|
||||
uint8_t difficulty; // 0-3 (always 0 on Episode 3)
|
||||
uint8_t battle_mode; // 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
|
||||
uint8_t challenge_mode; // 0 or 1
|
||||
uint8_t episode; // 1-4 on V3+; unused on DC/PC
|
||||
uint8_t episode; // 1-4 on V3+ (3 on Episode 3); unused on DC/PC
|
||||
};
|
||||
struct C_CreateGame_DC_GC_C1_EC : C_CreateGame<char> { };
|
||||
struct C_CreateGame_PC_C1 : C_CreateGame<char16_t> { };
|
||||
@@ -1537,7 +1636,38 @@ struct C_SetBlockedSenders_GC_BB_C6 {
|
||||
// Same as 60, but only send to Episode 3 clients.
|
||||
|
||||
// CA (C->S): Server data request (Episode 3)
|
||||
// TODO: Document this. It does many different things.
|
||||
// The format is generally the same as the subcommand-based commands (60, 62,
|
||||
// etc.), but the server is expected to respond to the command instead of
|
||||
// forwarding it. (The client has no handler for CA commands at all.)
|
||||
// Generally a CA command looks like this:
|
||||
// CA 00 SS SS B3 TT 00 00 WW 00 00 00 ...
|
||||
// S = command size
|
||||
// T = subcommand size in uint32_ts (== (S / 4) - 1)
|
||||
// W = subcommand number
|
||||
// We refer to Episode 3 server data commands as CAxWW, where W comes from the
|
||||
// format above. The server data commands are:
|
||||
// CAx0B (T=05) - Unknown
|
||||
// CAx0C (T=05) - Unknown
|
||||
// CAx0D (T=07) - Unknown
|
||||
// CAx0E (T=05) - Unknown
|
||||
// CAx0F (T=07) - Unknown
|
||||
// CAx10 (T=06) - Unknown
|
||||
// CAx11 (T=1E) - Unknown
|
||||
// CAx12 (T=05) - Unknown
|
||||
// CAx13 (T=AF) - Update game state (?)
|
||||
// CAx14 (T=1B) - Update playfield state (?)
|
||||
// CAx1B (T=09) - Update names (?)
|
||||
// CAx1D (T=04) - Unknown
|
||||
// CAx21 (T=05) - Unknown
|
||||
// CAx28 (T=05) - Unknown
|
||||
// CAx2B (T=05) - Unknown
|
||||
// CAx34 (T=05) - Unknown
|
||||
// CAx3A (T=04) - Unknown
|
||||
// CAx40 (T=04) - Map list request. See send_ep3_map_list for server response.
|
||||
// CAx41 (T=05) - Map data request. See send_ep3_map_data for server response.
|
||||
// CAx48 (T=05) - Unknown
|
||||
// CAx49 (T=C1) - Unknown
|
||||
// TODO: Document the above commands that are currently unknown.
|
||||
|
||||
// CB: Broadcast command (Episode 3)
|
||||
// Same as 60, but only send to Episode 3 clients.
|
||||
@@ -1562,12 +1692,15 @@ struct S_Unknown_GC_Ep3_CC {
|
||||
// D0 (C->S): Execute trade via trade window (GC/BB)
|
||||
// General sequence: client sends D0, server sends D1 to that client, server
|
||||
// sends D3 to other client, server sends D4 to both (?) clients.
|
||||
// Format unknown
|
||||
// Format unknown. On PSO GC it appears to be always 0x288 bytes in size; on BB
|
||||
// it is 0x28C bytes in size, implying that the format is the same between the
|
||||
// two versions (since BB headers are 4 bytes longer).
|
||||
|
||||
// D1 (S->C): Confirm trade to initiator (GC/BB)
|
||||
// No arguments
|
||||
|
||||
// D2: Invalid command
|
||||
// D2 (C->S): Unknown (used in trade sequence)
|
||||
// No arguments
|
||||
|
||||
// D3 (S->C): Execute trade with accepter (GC/BB)
|
||||
// Format unknown; appears to be same as D0.
|
||||
@@ -1640,14 +1773,18 @@ struct C_VerifyLicense_GC_DB {
|
||||
ptext<char, 0x30> password;
|
||||
};
|
||||
|
||||
// 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_VerifyLicense_BB_DB {
|
||||
ptext<char, 0x10> unknown_a1;
|
||||
ptext<char, 0x10> unknown_a2;
|
||||
ptext<char, 0x10> unknown_a3;
|
||||
ptext<char, 0x10> unknown_a4;
|
||||
ptext<char, 0x30> unknown_a5;
|
||||
ptext<char, 0x30> unknown_a6;
|
||||
ptext<char, 0x30> unknown_a7;
|
||||
// Note: These four fields are likely the same as those used in BB's 9E
|
||||
ptext<char, 0x10> unknown_a3; // Always blank?
|
||||
ptext<char, 0x10> unknown_a4; // == "?"
|
||||
ptext<char, 0x10> unknown_a5; // Always blank?
|
||||
ptext<char, 0x10> unknown_a6; // Always blank?
|
||||
le_uint32_t sub_version;
|
||||
ptext<char, 0x30> username;
|
||||
ptext<char, 0x30> password;
|
||||
ptext<char, 0x30> game_tag; // "psopc2"
|
||||
};
|
||||
|
||||
// DC: Player menu state (Episode 3)
|
||||
@@ -1691,7 +1828,39 @@ struct S_RareMonsterConfig_BB_DE {
|
||||
le_uint16_t data[16];
|
||||
};
|
||||
|
||||
// DF: Invalid command
|
||||
// DF (C->S): Unknown (BB)
|
||||
// This command has many subcommands. It's not clear what any of them do.
|
||||
|
||||
struct C_Unknown_BB_01DF {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
struct C_Unknown_BB_02DF {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
struct C_Unknown_BB_03DF {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
struct C_Unknown_BB_04DF {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
struct C_Unknown_BB_05DF {
|
||||
le_uint32_t unknown_a1;
|
||||
ptext<char16_t, 0x0C> unknown_a2;
|
||||
};
|
||||
|
||||
struct C_Unknown_BB_06DF {
|
||||
parray<le_uint32_t, 3> unknown_a1;
|
||||
};
|
||||
|
||||
struct C_Unknown_BB_07DF {
|
||||
le_uint32_t unused1; // Always 0xFFFFFFFF
|
||||
le_uint32_t unused2; // Always 0
|
||||
parray<le_uint32_t, 5> unknown_a1;
|
||||
};
|
||||
|
||||
// E0 (S->C): Tournament list (Episode 3)
|
||||
|
||||
@@ -1718,11 +1887,13 @@ struct S_Unknown_GC_Ep3_E1 {
|
||||
};
|
||||
|
||||
// E2 (C->S): Tournament control (Episode 3)
|
||||
// Flag = 0 => request tournament list (server responds with E0)
|
||||
// Flag = 1 => check tournament
|
||||
// Flag = 2 => cancel tournament entry
|
||||
// Flag = 3 => create tournament spectator team
|
||||
// Flag = 4 => join tournament spectator team
|
||||
// No arguments (in any of its forms)
|
||||
// Command meaning differs based on the value of header.flag. Specifically:
|
||||
// header.flag = 00 => request tournament list (server responds with E0)
|
||||
// header.flag = 01 => check tournament
|
||||
// header.flag = 02 => cancel tournament entry
|
||||
// header.flag = 03 => create tournament spectator team
|
||||
// header.flag = 04 => join tournament spectator team
|
||||
|
||||
// E2 (S->C): Tournament entry control (Episode 3)
|
||||
|
||||
@@ -1795,6 +1966,10 @@ struct S_PlayerPreview_NoPlayer_BB_00E4 {
|
||||
le_uint32_t error; // 2 = no player present
|
||||
};
|
||||
|
||||
// E5 (C->S): CARD lobby game? (Episode 3)
|
||||
// Appears to be the same as E4 (C->S), but also appears never to be sent by the
|
||||
// client.
|
||||
|
||||
// E5 (S->C): Player preview (BB)
|
||||
// E5 (C->S): Create character (BB)
|
||||
|
||||
@@ -1803,8 +1978,25 @@ struct SC_PlayerPreview_CreateCharacter_BB_00E5 {
|
||||
PlayerDispDataBBPreview preview;
|
||||
};
|
||||
|
||||
// E6: Spectator team list (Episode 3)
|
||||
// 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 == 1, this command is presumably used for joining a
|
||||
// spectator team (TODO: verify this). The following arguments are given in this
|
||||
// form:
|
||||
|
||||
struct C_JoinSpectatorTeam_GC_Ep3_E6_Flag01 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
};
|
||||
|
||||
// E6 (S->C): Spectator team list (Episode 3)
|
||||
// Same format as 08 command.
|
||||
// TODO: There are two separate functions on the client for this, one that sends
|
||||
// with header.flag == 0, and one that sends with header.flag == 1. Figure out
|
||||
// the difference between these two and document it here.
|
||||
|
||||
// E6 (S->C): Set guild card number and update client config (BB)
|
||||
|
||||
@@ -1817,11 +2009,22 @@ struct S_ClientInit_BB_00E6 {
|
||||
le_uint32_t caps; // should be 0x00000102
|
||||
};
|
||||
|
||||
// E7 (C->S): Create spectator team (Episode 3)
|
||||
|
||||
struct C_CreateSpectatorTeam_GC_Ep3_E7 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
ptext<char, 0x10> name;
|
||||
ptext<char, 0x10> password;
|
||||
le_uint32_t unused;
|
||||
};
|
||||
|
||||
// E7 (S->C): Unknown (Episode 3)
|
||||
// Same format as E2 command.
|
||||
|
||||
// E7: Save or load full player data (BB)
|
||||
// See export_bb_player_data() in Player.cc for format.
|
||||
// TODO: Verify full breakdown from send_E7 in BB disassembly.
|
||||
|
||||
// E8 (S->C): Join spectator team (probably) (Episode 3)
|
||||
|
||||
@@ -1873,14 +2076,43 @@ struct S_AcceptClientChecksum_BB_02E8 {
|
||||
// No arguments
|
||||
// Server should send the guild card file data using DC commands.
|
||||
|
||||
// 04E8 (C->S): Accept sent guild card
|
||||
// 04E8 (C->S): Add guild card
|
||||
|
||||
struct C_AddOrUpdateGuildCard_BB_04E8_06E8_07E8 {
|
||||
// TODO: Document this format
|
||||
parray<uint8_t, 0x0108> unknown_a1;
|
||||
};
|
||||
|
||||
// 05E8 (C->S): Delete guild card
|
||||
|
||||
struct C_DeleteGuildCard_BB_05E8_08E8 {
|
||||
le_uint32_t guild_card_number;
|
||||
};
|
||||
|
||||
// 06E8 (C->S): Set guild card text
|
||||
// 07E8 (C->S): Block user
|
||||
// 08E8 (C->S): Unblock user
|
||||
// Same format as 04E8.
|
||||
|
||||
// 07E8 (C->S): Add blocked user
|
||||
// Same format as 04E8.
|
||||
|
||||
// 08E8 (C->S): Delete blocked user
|
||||
// Same format as 05E8.
|
||||
|
||||
// 09E8 (C->S): Write comment
|
||||
|
||||
struct C_WriteGuildCardComment_BB_09E8 {
|
||||
ptext<char16_t, 0x5A> comment;
|
||||
};
|
||||
|
||||
// 0AE8 (C->S): Set guild card position in list
|
||||
|
||||
struct C_MoveGuildCard_BB_0AE8 {
|
||||
// TODO: One of these is the GC number, the other is the position. Figure out
|
||||
// which is which.
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
};
|
||||
|
||||
// E9 (S->C): Other player left spectator team (probably) (Episode 3)
|
||||
// Same format as 66/69 commands.
|
||||
|
||||
@@ -1896,24 +2128,83 @@ struct S_Unknown_GC_Ep3_EA {
|
||||
// EA: Team control (BB)
|
||||
|
||||
// 01EA (C->S): Create team
|
||||
|
||||
struct C_CreateTeam_BB_01EA {
|
||||
ptext<char16_t, 0x10> name;
|
||||
};
|
||||
|
||||
// 03EA (C->S): Add team member
|
||||
|
||||
struct C_AddOrRemoveTeamMember_BB_03EA_05EA {
|
||||
le_uint32_t guild_card_number;
|
||||
};
|
||||
|
||||
// 05EA (C->S): Remove team member
|
||||
// Same format as 03EA.
|
||||
|
||||
// 07EA (C->S): Team chat
|
||||
|
||||
// 08EA (C->S): Team admin
|
||||
// No arguments
|
||||
|
||||
// 0DEA (C->S): Unknown
|
||||
// No arguments
|
||||
|
||||
// 0EEA (S->C): Unknown
|
||||
|
||||
// 0FEA (C->S): Set team flag
|
||||
|
||||
struct C_SetTeamFlag_BB_0FEA {
|
||||
parray<uint8_t, 0x800> data;
|
||||
};
|
||||
|
||||
// 10EA: Delete team
|
||||
// No arguments
|
||||
|
||||
// 11EA (C->S): Promote team member
|
||||
// TODO: header.flag is used for this command. Figure out what it's for.
|
||||
|
||||
struct C_PromoteTeamMember_BB_11EA {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
// 12EA (S->C): Unknown
|
||||
|
||||
// 13EA: Unknown
|
||||
// No arguments
|
||||
|
||||
// 14EA (C->S): Unknown
|
||||
// No arguments. Client always sends 1 in the header.flag field.
|
||||
|
||||
// 15EA (S->C): Unknown
|
||||
|
||||
// 18EA: Membership information
|
||||
// No arguments (C->S)
|
||||
// TODO: Document S->C format
|
||||
|
||||
// 19EA: Privilege list
|
||||
// No arguments (C->S)
|
||||
// TODO: Document S->C format
|
||||
|
||||
// 1AEA: Unknown
|
||||
// 1CEA (C->S): Ranking information
|
||||
|
||||
// 1BEA (C->S): Unknown
|
||||
// header.flag is used, but no other arguments
|
||||
|
||||
// 1CEA (C->S): Unknown
|
||||
// No arguments
|
||||
|
||||
// 1EEA (C->S): Unknown
|
||||
// header.flag is used, but it's unknown what the value means.
|
||||
|
||||
struct C_Unknown_BB_1EEA {
|
||||
ptext<char16_t, 0x10> unknown_a1;
|
||||
};
|
||||
|
||||
// 20EA (C->S): Unknown
|
||||
// header.flag is used, but no other arguments
|
||||
|
||||
// EB (S->C): Unknown (Episode 3)
|
||||
// Looks like another lobby-joining type of command.
|
||||
|
||||
@@ -1963,6 +2254,7 @@ union C_UpdateAccountData_BB_ED {
|
||||
parray<uint8_t, 0x38> pad_config; // 05ED
|
||||
parray<uint8_t, 0x28> tech_menu; // 06ED
|
||||
parray<uint8_t, 0xE8> customize; // 07ED
|
||||
parray<uint8_t, 0x140> challenge_battle_config; // 08ED
|
||||
};
|
||||
|
||||
// EE (S->C): Unknown (Episode 3)
|
||||
@@ -1970,28 +2262,39 @@ union C_UpdateAccountData_BB_ED {
|
||||
// the relevant flag values match the Episodes 1 & 2 trade window commands, this
|
||||
// may be used for trading cards.
|
||||
|
||||
union S_Unknown_GC_Ep3_EE {
|
||||
struct {
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
struct Entry {
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
};
|
||||
Entry entries[4];
|
||||
} flag_D3;
|
||||
struct {
|
||||
struct C_Unknown_GC_Ep3_EE_FlagD0 {
|
||||
parray<le_uint32_t, 9> unknown_a1;
|
||||
};
|
||||
|
||||
struct S_Unknown_GC_Ep3_EE_FlagD1 {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
// EE D2 04 00 (C->S) - no arguments
|
||||
|
||||
struct S_Unknown_GC_Ep3_EE_FlagD3 {
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
struct Entry {
|
||||
le_uint32_t unknown_a1;
|
||||
} flag_D1;
|
||||
struct {
|
||||
le_uint32_t unknown_a1;
|
||||
} flag_D4;
|
||||
le_uint32_t unknown_a2;
|
||||
};
|
||||
Entry entries[4];
|
||||
};
|
||||
|
||||
// EE D4 04 00 (C->S) - no arguments
|
||||
|
||||
struct S_Unknown_GC_Ep3_EE_FlagD4 {
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
// 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.
|
||||
|
||||
// EF (C->S): Unknown (Episode 3)
|
||||
// No arguments
|
||||
|
||||
// EF (S->C): Unknown (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_EF {
|
||||
|
||||
@@ -117,7 +117,7 @@ static HandlerResult process_server_gc_9A(shared_ptr<ServerState>,
|
||||
return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
C_LoginWithUnusedSpace_GC_9E cmd;
|
||||
C_LoginExtended_GC_9E cmd;
|
||||
if (session.remote_guild_card_number == 0) {
|
||||
cmd.player_tag = 0xFFFF0000;
|
||||
cmd.guild_card_number = 0xFFFFFFFF;
|
||||
@@ -127,7 +127,8 @@ static HandlerResult process_server_gc_9A(shared_ptr<ServerState>,
|
||||
}
|
||||
cmd.unused = 0;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.unused2.data()[1] = 1;
|
||||
cmd.is_extended = session.remote_guild_card_number ? 0 : 1;
|
||||
cmd.unknown_a1 = 1;
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "", session.license->serial_number);
|
||||
cmd.access_key = session.license->access_key;
|
||||
cmd.serial_number2 = cmd.serial_number;
|
||||
@@ -139,8 +140,8 @@ static HandlerResult process_server_gc_9A(shared_ptr<ServerState>,
|
||||
// right after the client config data
|
||||
|
||||
session.server_channel.send(
|
||||
0x9E, 0x01, &cmd,
|
||||
sizeof(C_LoginWithUnusedSpace_GC_9E) - (session.remote_guild_card_number ? sizeof(cmd.unused_space) : 0));
|
||||
0x9E, 0x01, &cmd,
|
||||
cmd.is_extended ? sizeof(C_LoginExtended_GC_9E) : sizeof(C_Login_GC_9E));
|
||||
return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
@@ -209,7 +210,8 @@ static HandlerResult process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
|
||||
}
|
||||
cmd.unused = 0xFFFFFFFFFFFF0000;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.unused2.data()[1] = 1;
|
||||
cmd.is_extended = 0;
|
||||
cmd.unknown_a1 = 1;
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "",
|
||||
session.license->serial_number);
|
||||
cmd.access_key = session.license->access_key;
|
||||
|
||||
+3
-3
@@ -209,7 +209,7 @@ void ProxyServer::on_client_connect(
|
||||
parray<uint8_t, 0x30> client_key;
|
||||
random_data(server_key.data(), server_key.bytes());
|
||||
random_data(client_key.data(), client_key.bytes());
|
||||
auto cmd = prepare_server_init_contents_bb(server_key, client_key);
|
||||
auto cmd = prepare_server_init_contents_bb(server_key, client_key, false);
|
||||
session->channel.send(0x03, 0x00, &cmd, sizeof(cmd));
|
||||
// TODO: Is this actually needed?
|
||||
// bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
|
||||
@@ -265,7 +265,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
throw runtime_error("command is not 9D");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_PC_9D>(
|
||||
data, sizeof(C_Login_PC_9D), sizeof(C_LoginWithUnusedSpace_PC_9D));
|
||||
data, sizeof(C_Login_PC_9D), sizeof(C_LoginExtended_PC_9D));
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
@@ -278,7 +278,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
throw runtime_error("command is not 9E");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_GC_9E>(
|
||||
data, sizeof(C_Login_GC_9E), sizeof(C_LoginWithUnusedSpace_GC_9E));
|
||||
data, sizeof(C_Login_GC_9E), sizeof(C_LoginExtended_GC_9E));
|
||||
license = session->server->state->license_manager->verify_gc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
|
||||
+70
-33
@@ -79,7 +79,7 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
|
||||
}
|
||||
|
||||
case ServerBehavior::LOGIN_SERVER:
|
||||
send_server_init(s, c, true);
|
||||
send_server_init(s, c, true, false);
|
||||
if (s->pre_lobby_event) {
|
||||
send_change_event(c, s->pre_lobby_event);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
|
||||
case ServerBehavior::DATA_SERVER_BB:
|
||||
case ServerBehavior::PATCH_SERVER:
|
||||
case ServerBehavior::LOBBY_SERVER:
|
||||
send_server_init(s, c, false);
|
||||
send_server_init(s, c, false, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -257,11 +257,11 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
const C_Login_PC_9D* base_cmd;
|
||||
if (command == 0x9D) {
|
||||
base_cmd = &check_size_t<C_Login_PC_9D>(data,
|
||||
sizeof(C_Login_PC_9D), sizeof(C_Login_PC_9D) + 0x84);
|
||||
sizeof(C_Login_PC_9D), sizeof(C_LoginExtended_PC_9D));
|
||||
|
||||
} else if (command == 0x9E) {
|
||||
const auto& cmd = check_size_t<C_Login_GC_9E>(data,
|
||||
sizeof(C_Login_GC_9E), sizeof(C_Login_GC_9E) + 0x64);
|
||||
sizeof(C_Login_GC_9E), sizeof(C_LoginExtended_GC_9E));
|
||||
base_cmd = &cmd;
|
||||
|
||||
try {
|
||||
@@ -391,7 +391,7 @@ void process_return_client_config(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
|
||||
void process_client_checksum(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 96
|
||||
check_size_t<C_ClientChecksum_GC_96>(data);
|
||||
check_size_t<C_CharSaveInfo_GC_BB_96>(data);
|
||||
send_server_time(c);
|
||||
}
|
||||
|
||||
@@ -722,20 +722,68 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
}
|
||||
|
||||
void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 10
|
||||
uint16_t, uint32_t flags, const string& data) { // 10
|
||||
bool uses_unicode = ((c->version == GameVersion::PC) || (c->version == GameVersion::BB));
|
||||
|
||||
const auto& cmd = check_size_t<C_MenuSelection>(data,
|
||||
sizeof(C_MenuSelection), sizeof(C_MenuSelection) + 0x10 * (1 + uses_unicode));
|
||||
uint32_t menu_id;
|
||||
uint32_t item_id;
|
||||
u16string password;
|
||||
u16string unknown_a1;
|
||||
|
||||
switch (cmd.menu_id) {
|
||||
if (flags == 0) {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_10_Flag00>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
} else if (flags == 1) {
|
||||
if (uses_unicode) {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_PC_BB_10_Flag01>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
unknown_a1 = cmd.unknown_a1;
|
||||
} else {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_DC_GC_10_Flag01>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
unknown_a1 = decode_sjis(cmd.unknown_a1);
|
||||
}
|
||||
} else if (flags == 2) {
|
||||
if (uses_unicode) {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_PC_BB_10_Flag02>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
password = cmd.password;
|
||||
} else {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_DC_GC_10_Flag02>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
password = decode_sjis(cmd.password);
|
||||
}
|
||||
} else if (flags == 3) {
|
||||
if (uses_unicode) {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_PC_BB_10_Flag03>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
unknown_a1 = cmd.unknown_a1;
|
||||
password = cmd.password;
|
||||
} else {
|
||||
const auto& cmd = check_size_t<C_MenuSelection_DC_GC_10_Flag03>(data);
|
||||
menu_id = cmd.menu_id;
|
||||
item_id = cmd.item_id;
|
||||
unknown_a1 = decode_sjis(cmd.unknown_a1);
|
||||
password = decode_sjis(cmd.password);
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("invalid flag");
|
||||
}
|
||||
|
||||
switch (menu_id) {
|
||||
case MenuID::MAIN: {
|
||||
switch (cmd.item_id) {
|
||||
switch (item_id) {
|
||||
case MainMenuItemID::GO_TO_LOBBY: {
|
||||
c->should_send_to_lobby_server = true;
|
||||
if (!(c->flags & Client::Flag::SAVE_ENABLED)) {
|
||||
send_command(c, 0x97, 0x01);
|
||||
c->flags |= Client::Flag::SAVE_ENABLED;
|
||||
send_command(c, 0x97, 0x01);
|
||||
send_update_client_config(c);
|
||||
} else {
|
||||
static const vector<string> version_to_port_name({
|
||||
@@ -802,13 +850,13 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
case MenuID::INFORMATION: {
|
||||
if (cmd.item_id == InformationMenuItemID::GO_BACK) {
|
||||
if (item_id == InformationMenuItemID::GO_BACK) {
|
||||
c->flags &= ~Client::Flag::IN_INFORMATION_MENU;
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
|
||||
} else {
|
||||
try {
|
||||
send_message_box(c, s->information_contents->at(cmd.item_id).c_str());
|
||||
send_message_box(c, s->information_contents->at(item_id).c_str());
|
||||
} catch (const out_of_range&) {
|
||||
send_message_box(c, u"$C6No such information exists.");
|
||||
}
|
||||
@@ -817,13 +865,13 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
case MenuID::PROXY_DESTINATIONS: {
|
||||
if (cmd.item_id == ProxyDestinationsMenuItemID::GO_BACK) {
|
||||
if (item_id == ProxyDestinationsMenuItemID::GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
|
||||
} else {
|
||||
const pair<string, uint16_t>* dest = nullptr;
|
||||
try {
|
||||
dest = &s->proxy_destinations_for_version(c->version).at(cmd.item_id);
|
||||
dest = &s->proxy_destinations_for_version(c->version).at(item_id);
|
||||
} catch (const out_of_range&) { }
|
||||
|
||||
if (!dest) {
|
||||
@@ -855,7 +903,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
case MenuID::GAME: {
|
||||
auto game = s->find_lobby(cmd.item_id);
|
||||
auto game = s->find_lobby(item_id);
|
||||
if (!game) {
|
||||
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it no\nlonger exists.");
|
||||
break;
|
||||
@@ -887,17 +935,6 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES)) {
|
||||
ptext<char16_t, 0x10> password;
|
||||
if (data.size() > sizeof(C_MenuSelection)) {
|
||||
if (uses_unicode) {
|
||||
size_t max_chars = (data.size() - sizeof(C_MenuSelection)) / sizeof(char16_t);
|
||||
password.assign(cmd.password.pcbb, max_chars);
|
||||
} else {
|
||||
size_t max_chars = (data.size() - sizeof(C_MenuSelection)) / sizeof(char);
|
||||
password = decode_sjis(cmd.password.dcgc, max_chars);
|
||||
}
|
||||
}
|
||||
|
||||
if (!game->password.empty() && (password != game->password)) {
|
||||
send_message_box(c, u"$C6Incorrect password.");
|
||||
break;
|
||||
@@ -925,7 +962,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
|
||||
auto quests = s->quest_index->filter(c->version,
|
||||
c->flags & Client::Flag::DCV1,
|
||||
static_cast<QuestCategory>(cmd.item_id & 0xFF));
|
||||
static_cast<QuestCategory>(item_id & 0xFF));
|
||||
if (quests.empty()) {
|
||||
send_lobby_message_box(c, u"$C6There are no quests\navailable in that\ncategory.");
|
||||
break;
|
||||
@@ -942,7 +979,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_lobby_message_box(c, u"$C6Quests are not available.");
|
||||
break;
|
||||
}
|
||||
auto q = s->quest_index->get(c->version, cmd.item_id);
|
||||
auto q = s->quest_index->get(c->version, item_id);
|
||||
if (!q) {
|
||||
send_lobby_message_box(c, u"$C6Quest does not exist.");
|
||||
break;
|
||||
@@ -1022,7 +1059,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
case MenuID::PATCHES:
|
||||
if (cmd.item_id == PatchesMenuItemID::GO_BACK) {
|
||||
if (item_id == PatchesMenuItemID::GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
|
||||
} else {
|
||||
@@ -1031,13 +1068,13 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
send_function_call(
|
||||
c, s->function_code_index->menu_item_id_to_patch_function.at(cmd.item_id));
|
||||
c, s->function_code_index->menu_item_id_to_patch_function.at(item_id));
|
||||
send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu());
|
||||
}
|
||||
break;
|
||||
|
||||
case MenuID::PROGRAMS:
|
||||
if (cmd.item_id == ProgramsMenuItemID::GO_BACK) {
|
||||
if (item_id == ProgramsMenuItemID::GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
|
||||
} else {
|
||||
@@ -1045,7 +1082,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
throw runtime_error("client does not support send_function_call");
|
||||
}
|
||||
|
||||
c->loading_dol_file = s->dol_file_index->item_id_to_file.at(cmd.item_id);
|
||||
c->loading_dol_file = s->dol_file_index->item_id_to_file.at(item_id);
|
||||
|
||||
// Send the first function call, which triggers the process of loading a
|
||||
// DOL file. This function call determines the necessary base address
|
||||
|
||||
+16
-10
@@ -97,7 +97,7 @@ static const char* anti_copyright = "This server is in no way affiliated, sponso
|
||||
static const char* dc_port_map_copyright = "DreamCast Port Map. Copyright SEGA Enterprises. 1999";
|
||||
static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999";
|
||||
static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.";
|
||||
// static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.";
|
||||
static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.";
|
||||
static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001";
|
||||
|
||||
S_ServerInit_DC_PC_GC_02_17_92_9B prepare_server_init_contents_dc_pc_gc(
|
||||
@@ -140,26 +140,32 @@ void send_server_init_dc_pc_gc(shared_ptr<Client> c,
|
||||
|
||||
S_ServerInit_BB_03 prepare_server_init_contents_bb(
|
||||
const parray<uint8_t, 0x30>& server_key,
|
||||
const parray<uint8_t, 0x30>& client_key) {
|
||||
const parray<uint8_t, 0x30>& client_key,
|
||||
bool use_secondary_message) {
|
||||
S_ServerInit_BB_03 cmd;
|
||||
cmd.copyright = bb_game_server_copyright;
|
||||
cmd.copyright = use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright;
|
||||
cmd.server_key = server_key;
|
||||
cmd.client_key = client_key;
|
||||
cmd.after_message = anti_copyright;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
bool use_secondary_message) {
|
||||
parray<uint8_t, 0x30> server_key;
|
||||
parray<uint8_t, 0x30> client_key;
|
||||
random_data(server_key.data(), server_key.bytes());
|
||||
random_data(client_key.data(), client_key.bytes());
|
||||
auto cmd = prepare_server_init_contents_bb(server_key, client_key);
|
||||
send_command_t(c, 0x03, 0x00, cmd);
|
||||
auto cmd = prepare_server_init_contents_bb(server_key, client_key, use_secondary_message);
|
||||
send_command_t(c, use_secondary_message ? 0x9B : 0x03, 0x00, cmd);
|
||||
|
||||
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
||||
static const string primary_expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
||||
static const string secondary_expected_first_data("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8);
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt(new PSOBBMultiKeyDetectorEncryption(
|
||||
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
|
||||
s->bb_private_keys,
|
||||
use_secondary_message ? secondary_expected_first_data : primary_expected_first_data,
|
||||
cmd.client_key.data(),
|
||||
sizeof(cmd.client_key)));
|
||||
c->channel.crypt_in = detector_crypt;
|
||||
c->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
|
||||
detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
|
||||
@@ -180,7 +186,7 @@ void send_server_init_patch(shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
void send_server_init(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
bool initial_connection) {
|
||||
bool initial_connection, bool use_secondary_message) {
|
||||
switch (c->version) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
@@ -191,7 +197,7 @@ void send_server_init(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_server_init_patch(c);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_server_init_bb(s, c);
|
||||
send_server_init_bb(s, c, use_secondary_message);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
|
||||
+3
-2
@@ -102,9 +102,10 @@ S_ServerInit_DC_PC_GC_02_17_92_9B prepare_server_init_contents_dc_pc_gc(
|
||||
uint32_t client_key);
|
||||
S_ServerInit_BB_03 prepare_server_init_contents_bb(
|
||||
const parray<uint8_t, 0x30>& server_key,
|
||||
const parray<uint8_t, 0x30>& client_key);
|
||||
const parray<uint8_t, 0x30>& client_key,
|
||||
bool use_secondary_message);
|
||||
void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
bool initial_connection);
|
||||
bool initial_connection, bool use_secondary_message);
|
||||
void send_update_client_config(std::shared_ptr<Client> c);
|
||||
|
||||
void send_function_call(
|
||||
|
||||
Reference in New Issue
Block a user