diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index bdc3da93..23e995a0 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1297,7 +1297,7 @@ struct S_JoinGame_XB_64 : S_JoinGameT_DC_PC { uint8_t episode = 0; uint8_t xb_enable_voice_chat = 1; parray unused; - S_UnknownXBVoiceChatConfig unknown_a1; + S_UnknownXBVoiceChatConfig voice_chat_config; } __packed_ws__(S_JoinGame_XB_64, 0x1D8); struct S_JoinGame_BB_64 : S_JoinGameT_DC_PC { @@ -1371,7 +1371,7 @@ check_struct_size(S_JoinLobby_BB_65_67_68, 0x3D8C); struct S_JoinLobby_XB_65_67_68 { LobbyFlags lobby_flags; - S_UnknownXBVoiceChatConfig unknown_a4; + S_UnknownXBVoiceChatConfig voice_chat_config; struct Entry { PlayerLobbyDataXB lobby_data; PlayerInventory inventory; @@ -1998,8 +1998,10 @@ struct C_LoginExtended_GC_9E : C_Login_GC_9E { } __packed_ws__(C_LoginExtended_GC_9E, 0x14C); struct C_Login_XB_9E : C_Login_DC_PC_GC_9D { - /* 00C8 */ parray unused; + /* 00C8 */ parray unknown_a4; /* 00E8 */ XBNetworkLocation netloc; + // By default, this array is initialized with [0x5A2773DD, 0xD82B2345, + // 0x4FF904D5] on 4OEU (US TU version). /* 0118 */ parray unknown_a1a; /* 0124 */ le_uint32_t xb_user_id_high = 0; /* 0128 */ le_uint32_t xb_user_id_low = 0; @@ -2412,12 +2414,11 @@ struct S_UpdateMediaHeader_Ep3_B9 { // BA: Meseta transaction (Episode 3) // This command is not valid on Episode 3 Trial Edition. // header.flag specifies the transaction purpose. Specific known values: -// 00 = unknown // 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) +// 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 +// 04 = Insufficient Meseta (S->C; request_token must match the last token sent // by client) struct C_MesetaTransaction_Ep3_BA { @@ -2432,33 +2433,36 @@ struct S_MesetaTransaction_Ep3_BA { le_uint32_t request_token = 0; // Should match the token sent by the client } __packed_ws__(S_MesetaTransaction_Ep3_BA, 0x0C); -// BB (S->C): Tournament match information (Episode 3) -// This command is not valid on Episode 3 Trial Edition. Because of this, it -// must have been added fairly late in development, but it seems to be unused, -// perhaps because the E1/E3 commands are generally more useful... but the E1/E3 -// commands exist in Trial Edition! So why was this added? Was it just never -// finished? We may never know... -// header.flag is the number of valid match entries. +// 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. +// header.flag is the number of game entries. -struct S_TournamentMatchInformation_Ep3_BB { - pstring tournament_name; - struct TeamEntry { - le_uint16_t win_count = 0; - le_uint16_t is_active = 0; - pstring name; - } __packed_ws__(TeamEntry, 0x24); - parray team_entries; - le_uint16_t num_teams = 0; - le_uint16_t unknown_a3 = 0; // Probably actually unused - struct MatchEntry { +struct Ep3TournamentBracketEntry { // Also used in commands CC and E3 + le_uint16_t win_count = 0; + le_uint16_t is_active = 0; + pstring team_name; + parray unused; +} __packed_ws__(Ep3TournamentBracketEntry, 0x24); + +struct S_TournamentSpectatorTeamList_Ep3_BB { + /* 0000 */ pstring tournament_name; + /* 0020 */ parray bracket_entries; + /* 04A0 */ le_uint16_t num_teams = 0; + /* 04A2 */ le_uint16_t players_per_team = 0; + struct GameEntry { pstring name; uint8_t locked = 0; uint8_t count = 0; uint8_t max_count = 0; - uint8_t unused = 0; - } __packed_ws__(MatchEntry, 0x24); - parray match_entries; -} __packed_ws__(S_TournamentMatchInformation_Ep3_BB, 0xDA4); + uint8_t unknown_a1 = 0; // Possibly unused + } __packed_ws__(GameEntry, 0x24); + /* 04A4 */ parray match_entries; + /* 0DA4 */ +} __packed_ws__(S_TournamentSpectatorTeamList_Ep3_BB, 0xDA4); // BC: Invalid command // BD: Invalid command @@ -2646,16 +2650,11 @@ struct S_ConfirmTournamentEntry_Ep3_CC { pstring tournament_name; le_uint16_t num_teams = 0; le_uint16_t players_per_team = 0; - le_uint16_t unknown_a2 = 0; - le_uint16_t unknown_a3 = 0; + le_uint16_t unused1 = 0; + le_uint16_t unused2 = 0; pstring server_name; pstring start_time; // e.g. "15:09:30" or "13:03 PST" - struct TeamEntry { - le_uint16_t win_count = 0; - le_uint16_t is_active = 0; - pstring name; - } __packed_ws__(TeamEntry, 0x24); - parray team_entries; + parray bracket_entries; } __packed_ws__(S_ConfirmTournamentEntry_Ep3_CC, 0x508); // CD: Invalid command @@ -2788,7 +2787,7 @@ struct C_VerifyAccount_V3_DB { pstring v1_access_key; // Unused pstring serial_number; // On XB, this is the XBL gamertag pstring access_key; // On XB, this is the XBL user ID - pstring unused2; + be_uint64_t hardware_id = 0; le_uint32_t sub_version = 0; pstring serial_number2; // On XB, this is the XBL gamertag pstring access_key2; // On XB, this is the XBL user ID @@ -2799,10 +2798,10 @@ struct C_VerifyAccount_V3_DB { // 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 unknown_a3; // Always blank? - pstring unknown_a4; // == "?" - pstring unknown_a5; // Always blank? - pstring unknown_a6; // Always blank? + pstring v1_serial_number; // Always blank? + pstring v1_access_key; // == "?" + pstring serial_number; // Always blank? + pstring access_key; // Always blank? le_uint32_t sub_version = 0; pstring username; pstring password; @@ -3066,18 +3065,12 @@ struct S_SyncSystemFile_BB_E2 { template struct S_TournamentGameDetailsBaseT_Ep3_E3 { // These fields are used only if the Rules pane is shown - /* 0000 */ pstring name; + /* 0000 */ pstring tournament_name; /* 0020 */ pstring map_name; /* 0040 */ RulesT rules; // 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; - pstring team_name; - parray unused; - } __packed_ws__(BracketEntry, 0x24); - /* 0054 */ parray bracket_entries; + /* 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 diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 5f73a06c..e30c14d1 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1309,7 +1309,12 @@ static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, string& total_meseta_earned = c->login->account->ep3_total_meseta_earned; } else { if (c->login->account->ep3_current_meseta < in_cmd.value) { - throw runtime_error("meseta overdraft not allowed"); + S_MesetaTransaction_Ep3_BA out_cmd = { + c->login->account->ep3_current_meseta, + c->login->account->ep3_total_meseta_earned, + in_cmd.request_token}; + send_command(c, command, 0x04, &out_cmd, sizeof(out_cmd)); + return; } c->login->account->ep3_current_meseta -= in_cmd.value; if (!s->is_replay) { diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 098d595f..4422905e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -3296,9 +3296,11 @@ void send_ep3_confirm_tournament_entry( cmd.num_teams = min(teams.size(), 0x20); cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; for (size_t z = 0; z < min(teams.size(), 0x20); z++) { - cmd.team_entries[z].win_count = teams[z]->num_rounds_cleared; - cmd.team_entries[z].is_active = teams[z]->is_active; - cmd.team_entries[z].name.encode(teams[z]->name, c->language()); + const auto& team = teams[z]; + auto& entry = cmd.bracket_entries[z]; + entry.win_count = team->num_rounds_cleared; + entry.is_active = team->is_active; + entry.team_name.encode(team->name, c->language()); } } send_command_t(c, 0xCC, tourn ? 0x01 : 0x00, cmd); @@ -3389,14 +3391,16 @@ void send_ep3_tournament_details_t( shared_ptr tourn) { S_TournamentGameDetailsBaseT_Ep3_E3 cmd; auto vm = tourn->get_map()->version(c->language()); - cmd.name.encode(tourn->get_name(), c->language()); + cmd.tournament_name.encode(tourn->get_name(), c->language()); cmd.map_name.encode(vm->map->name.decode(vm->language), c->language()); cmd.rules = tourn->get_rules(); const auto& teams = tourn->all_teams(); for (size_t z = 0; z < min(teams.size(), 0x20); z++) { - cmd.bracket_entries[z].win_count = teams[z]->num_rounds_cleared; - cmd.bracket_entries[z].is_active = teams[z]->is_active ? 1 : 0; - cmd.bracket_entries[z].team_name.encode(teams[z]->name, c->language()); + const auto& team = teams[z]; + auto& entry = cmd.bracket_entries[z]; + entry.win_count = team->num_rounds_cleared; + entry.is_active = team->is_active ? 1 : 0; + entry.team_name.encode(team->name, c->language()); } cmd.num_bracket_entries = teams.size(); cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; @@ -3440,7 +3444,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { if (tourn) { S_TournamentGameDetailsBaseT_Ep3_E3 cmd; - cmd.name.encode(l->name, c->language()); + cmd.tournament_name.encode(l->name, c->language()); auto vm = tourn->get_map()->version(c->language()); cmd.map_name.encode(vm->map->name.decode(vm->language), c->language());