implement ep3 extended game/tournament info commands

This commit is contained in:
Martin Michelsen
2022-12-11 11:04:11 -08:00
parent 14639c63e3
commit cceaf5efde
7 changed files with 247 additions and 83 deletions
+85 -35
View File
@@ -2074,9 +2074,9 @@ struct C_SetBlockedSenders_BB_C6 : C_SetBlockedSenders_C6<28> { } __packed__;
// Same as 60, but only send to Episode 3 clients.
// This command is identical to C9, except that CB is not valid on Episode 3
// Trial Edition (whereas C9 is valid).
// TODO: What's the difference here? The client-side handlers are identical, so
// presumably the server is supposed to do something different between the two
// commands, but it's not clear what that difference should be.
// TODO: Presumably this command is meant to be forwarded from spectator teams
// to the primary team, whereas 6x and C9 commands are not. I haven't verified
// this or implemented this behavior yet though.
// CC (S->C): Confirm tournament entry (Episode 3)
// This command is not valid on Episode 3 Trial Edition.
@@ -2351,17 +2351,24 @@ struct S_TournamentList_GC_Ep3_E0 {
// E0 (C->S): Request team and key config (BB)
// 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.
struct S_GameInformation_GC_Ep3_E1 {
/* 0004 */ ptext<char, 0x20> game_name;
struct Entry {
struct PlayerEntry {
ptext<char, 0x10> name; // From disp.name
ptext<char, 0x20> description; // Usually something like "FOmarl CLv30 J"
} __packed__;
/* 0024 */ parray<Entry, 4> entries;
/* 0024 */ parray<PlayerEntry, 4> player_entries;
/* 00E4 */ parray<uint8_t, 0x20> unknown_a3;
/* 0104 */ parray<uint8_t, 0x14> unknown_a4;
/* 0118 */ parray<uint8_t, 0x180> unknown_a5;
/* 0104 */ Episode3::Rules rules;
/* 0114 */ parray<uint8_t, 4> unknown_a4;
/* 0118 */ parray<PlayerEntry, 8> spectator_entries;
} __packed__;
// E2 (C->S): Tournament control (Episode 3)
@@ -2410,25 +2417,63 @@ struct S_TournamentEntryList_GC_Ep3_E2 {
// E2 (S->C): Team and key config (BB)
// See KeyAndTeamConfigBB in Player.hh for format
// E3 (S->C): Tournament info (Episode 3)
// 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.
struct S_TournamentInfo_GC_Ep3_E3 {
struct Entry {
struct S_TournamentGameDetails_GC_Ep3_E3 {
// These fields are used only if the Rules pane is shown
/* 0004/032C */ ptext<char, 0x20> name;
/* 0024/034C */ ptext<char, 0x20> map_name;
/* 0044/036C */ Episode3::Rules rules;
/* 0054/037C */ parray<uint8_t, 4> unknown_a1;
// This field is used only if the bracket pane is shown
struct BracketEntry {
le_uint16_t win_count = 0;
le_uint16_t is_active = 0;
ptext<char, 0x20> team_name;
ptext<char, 0x18> team_name;
parray<uint8_t, 8> unused;
} __packed__;
ptext<char, 0x20> name;
ptext<char, 0x20> map_name;
parray<uint8_t, 4> unknown_a1;
Episode3::Rules rules;
parray<Entry, 0x20> entries;
parray<uint8_t, 0xE0> unknown_a2;
le_uint16_t max_entries = 0;
le_uint16_t unknown_a3 = 1;
le_uint16_t unknown_a4 = 0;
le_uint16_t unknown_a5 = 0;
parray<uint8_t, 0x180> unknown_a6;
/* 0058/0380 */ parray<BracketEntry, 0x20> 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).
struct PlayerEntry {
ptext<char, 0x10> name;
ptext<char, 0x20> description; // Usually something like "RAmarl CLv24 E"
} __packed__;
struct TeamEntry {
ptext<char, 0x10> team_name;
parray<PlayerEntry, 2> players;
} __packed__;
/* 04D8/0800 */ parray<TeamEntry, 2> team_entries;
/* 05B8/08E0 */ le_uint16_t num_bracket_entries = 0;
/* 05BA/08E2 */ le_uint16_t players_per_team = 0;
/* 05BC/08E4 */ le_uint16_t unknown_a4 = 0;
/* 05BE/08E6 */ le_uint16_t num_spectators = 0;
/* 05C0/08E8 */ parray<PlayerEntry, 8> spectator_entries;
} __packed__;
// E3 (C->S): Player preview request (BB)
@@ -2769,10 +2814,10 @@ struct S_StreamFileChunk_BB_02EB {
// No arguments
// Server should respond with a 01EB command.
// EC: Create game (Episode 3)
// EC (C->S): Create game (Episode 3)
// Same format as C1; some fields are unused (e.g. episode, difficulty).
// EC: Leave character select (BB)
// EC (C->S): Leave character select (BB)
struct C_LeaveCharacterSelect_BB_00EC {
// Reason codes:
@@ -4586,6 +4631,7 @@ struct G_BankAction_BB_6xBD {
} __packed__;
// 6xBE: Sound chat (Episode 3; not Trial Edition)
// This appears to be the only subcommand ever sent with the CB command.
struct G_SoundChat_GC_Ep3_6xBE {
G_UnusedHeader header;
@@ -4743,8 +4789,8 @@ struct G_SetStateFlags_GC_Ep3_6xB4x03 {
// 6xB4x04: Update SC/FC short statuses
struct G_UpdateStats_GC_Ep3_6xB4x04 {
G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateStats_GC_Ep3_6xB4x04) / 4, 0, 0x04, 0, 0, 0};
struct G_UpdateShortStatuses_GC_Ep3_6xB4x04 {
G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateShortStatuses_GC_Ep3_6xB4x04) / 4, 0, 0x04, 0, 0, 0};
le_uint16_t client_id = 0;
le_uint16_t unused = 0;
// The slots in this array have heterogeneous meanings. Specifically:
@@ -5224,11 +5270,11 @@ struct G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D {
Episode3::Rules rules;
parray<uint8_t, 4> unknown_a1;
struct Entry {
uint8_t type = 0; // 1 = human, 2 = COM
uint8_t type = 0; // 0 = no player, 1 = human, 2 = COM
ptext<char, 0x10> player_name;
ptext<char, 0x10> deck_name; // Seems to only be used for COM players
ptext<char, 0x10> deck_name; // Only be used for COM players
parray<uint8_t, 5> unknown_a1;
parray<le_uint16_t, 0x1F> card_ids;
parray<le_uint16_t, 0x1F> card_ids; // Can be blank for human players
uint8_t client_id = 0; // Unused for COMs
uint8_t unknown_a4 = 0;
le_uint16_t unknown_a2 = 0;
@@ -5252,7 +5298,7 @@ struct G_MakeCardAuctionBid_GC_Ep3_6xB5x3E {
parray<uint8_t, 2> unused;
} __packed__;
// 6xB5x3F: Open menu
// 6xB5x3F: Open blocking menu
struct G_OpenBlockingMenu_GC_Ep3_6xB5x3F {
G_CardBattleCommandHeader header = {0xB5, sizeof(G_OpenBlockingMenu_GC_Ep3_6xB5x3F) / 4, 0, 0x3F, 0, 0, 0};
@@ -5382,7 +5428,11 @@ struct G_EndTurn_GC_Ep3_6xB3x48_CAx48 {
// verification at battle start time.) 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.
// 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.
struct G_CardCounts_GC_Ep3_6xB3x49_CAx49 {
G_CardServerDataCommandHeader header = {0xB3, sizeof(G_CardCounts_GC_Ep3_6xB3x49_CAx49) / 4, 0, 0x49, 0, 0, 0, 0, 0};
@@ -5524,10 +5574,10 @@ struct G_TournamentMatchResult_GC_Ep3_6xB4x51 {
struct G_Unknown_GC_Ep3_6xB4x52 {
G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_GC_Ep3_6xB4x52) / 4, 0, 0x52, 0, 0, 0};
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
le_uint16_t size = 0; // Number of valid bytes in the data field (clamped to 0xFF)
le_uint16_t unknown_a1 = 0; // Clamped to [0, 999] by the client
le_uint16_t unknown_a2 = 0; // Clamped to [0, 999] by the client
le_uint16_t unused = 0;
le_uint16_t size = 0; // Number of used bytes in data (clamped to 0xFF)
parray<uint8_t, 0x100> data;
} __packed__;