From 4741091b9ff5a4c1acae1f560d41c71dfefe0726 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 16 Sep 2023 09:57:28 -0700 Subject: [PATCH] fix client crash when creating spectator team --- src/ChatCommands.cc | 6 +++- src/CommandFormats.hh | 69 +++++++++++++++++++++++-------------------- src/Player.hh | 50 +++++++++++++++---------------- src/SendCommands.cc | 6 +++- 4 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 8d571fbc..80ed9529 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -593,8 +593,12 @@ static void server_command_playrec(shared_ptr s, shared_ptr shared_ptr record(new Episode3::BattleRecord(data)); shared_ptr battle_player( new Episode3::BattleRecordPlayer(record, s->game_server->get_base())); - create_game_generic(s, c, args.c_str(), u"", Episode::EP3, GameMode::NORMAL, + auto game = create_game_generic(s, c, args.c_str(), u"", Episode::EP3, GameMode::NORMAL, 0, flags, nullptr, battle_player); + if (game) { + s->change_client_lobby(c, game); + c->flags |= Client::Flag::LOADING; + } } } diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index be1cc998..b7b21faf 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3099,46 +3099,51 @@ struct SC_SyncCharacterSaveFile_BB_00E7 { // 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). struct S_JoinSpectatorTeam_GC_Ep3_E8 { - parray variations; // 04-84; unused + /* 0004 */ parray variations; // unused struct PlayerEntry { - PlayerLobbyDataDCGC lobby_data; // 0x20 bytes - PlayerInventory inventory; // 0x34C bytes - PlayerDispDataDCPCV3 disp; // 0xD0 bytes - } __packed__; // 0x43C bytes - parray players; // 84-1174 - uint8_t client_id = 0; - uint8_t leader_id = 0; - uint8_t disable_udp = 1; - uint8_t difficulty = 0; - uint8_t battle_mode = 0; - uint8_t event = 0; - uint8_t section_id = 0; - uint8_t challenge_mode = 0; - le_uint32_t rare_seed = 0; - uint8_t episode = 0; - uint8_t unused2 = 1; - uint8_t solo_mode = 0; - uint8_t unused3 = 0; + /* 0000 */ PlayerLobbyDataDCGC lobby_data; + /* 0020 */ PlayerInventory inventory; + /* 036C */ PlayerDispDataDCPCV3 disp; + /* 043C */ + } __packed__; + /* 0084 */ parray players; + /* 1174 */ uint8_t client_id = 0; + /* 1175 */ uint8_t leader_id = 0; + /* 1176 */ uint8_t disable_udp = 1; + /* 1177 */ uint8_t difficulty = 0; + /* 1178 */ uint8_t battle_mode = 0; + /* 1179 */ uint8_t event = 0; + /* 117A */ uint8_t section_id = 0; + /* 117B */ uint8_t challenge_mode = 0; + /* 117C */ le_uint32_t rare_seed = 0; + /* 1180 */ uint8_t episode = 0; + /* 1181 */ uint8_t unused2 = 1; + /* 1182 */ uint8_t solo_mode = 0; + /* 1183 */ uint8_t unused3 = 0; struct SpectatorEntry { - le_uint32_t player_tag = 0; - le_uint32_t guild_card_number = 0; - ptext name; - uint8_t present = 0; - uint8_t unknown_a3 = 0; - le_uint16_t level = 0; - parray unknown_a5; - parray unknown_a6; - } __packed__; // 0x38 bytes + /* 00 */ le_uint32_t player_tag = 0; + /* 04 */ le_uint32_t guild_card_number = 0; + /* 08 */ ptext name; + /* 28 */ uint8_t present = 0; + /* 29 */ uint8_t unknown_a3 = 0; + /* 2A */ le_uint16_t level = 0; + /* 2C */ parray unknown_a5; + /* 34 */ parray unknown_a6; + /* 38 */ + } __packed__; // 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. - parray entries; // 1184-1424 - ptext spectator_team_name; + /* 1184 */ parray entries; + /* 1424 */ ptext spectator_team_name; // This field doesn't appear to be actually used by the game, but some servers - // send it anyway (and the game presumably ignores it) - parray spectator_players; + // send it anyway (and the game ignores it) + /* 1444 */ parray spectator_players; + /* 3624 */ } __packed__; // E8 (C->S): Guild card commands (BB) diff --git a/src/Player.hh b/src/Player.hh index c2be3bf6..df199dc1 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -112,12 +112,12 @@ struct PlayerDispDataBB; struct PlayerStats { /* 00 */ CharacterStats char_stats; - /* 0E */ le_uint16_t unknown_a1; - /* 10 */ le_float unknown_a2; - /* 14 */ le_float unknown_a3; - /* 18 */ le_uint32_t level; - /* 1C */ le_uint32_t experience; - /* 20 */ le_uint32_t meseta; + /* 0E */ le_uint16_t unknown_a1 = 0; + /* 10 */ le_float unknown_a2 = 0.0; + /* 14 */ le_float unknown_a3 = 0.0; + /* 18 */ le_uint32_t level = 0; + /* 1C */ le_uint32_t experience = 0; + /* 20 */ le_uint32_t meseta = 0; /* 24 */ PlayerStats() noexcept; @@ -125,26 +125,26 @@ struct PlayerStats { struct PlayerVisualConfig { /* 00 */ ptext name; - /* 10 */ le_uint64_t unknown_a2; // Note: This is probably not actually a 64-bit int. - /* 18 */ le_uint32_t name_color; // RGBA - /* 1C */ uint8_t extra_model; + /* 10 */ le_uint64_t unknown_a2 = 0; // Note: This is probably not actually a 64-bit int. + /* 18 */ le_uint32_t name_color = 0xFFFFFFFF; // RGBA + /* 1C */ uint8_t extra_model = 0; /* 1D */ parray unused; - /* 2C */ le_uint32_t unknown_a3; - /* 30 */ uint8_t section_id; - /* 31 */ uint8_t char_class; - /* 32 */ uint8_t v2_flags; - /* 33 */ uint8_t version; - /* 34 */ le_uint32_t v1_flags; - /* 38 */ le_uint16_t costume; - /* 3A */ le_uint16_t skin; - /* 3C */ le_uint16_t face; - /* 3E */ le_uint16_t head; - /* 40 */ le_uint16_t hair; - /* 42 */ le_uint16_t hair_r; - /* 44 */ le_uint16_t hair_g; - /* 46 */ le_uint16_t hair_b; - /* 48 */ le_float proportion_x; - /* 4C */ le_float proportion_y; + /* 2C */ le_uint32_t unknown_a3 = 0; + /* 30 */ uint8_t section_id = 0; + /* 31 */ uint8_t char_class = 0; + /* 32 */ uint8_t v2_flags = 0; + /* 33 */ uint8_t version = 0; + /* 34 */ le_uint32_t v1_flags = 0; + /* 38 */ le_uint16_t costume = 0; + /* 3A */ le_uint16_t skin = 0; + /* 3C */ le_uint16_t face = 0; + /* 3E */ le_uint16_t head = 0; + /* 40 */ le_uint16_t hair = 0; + /* 42 */ le_uint16_t hair_r = 0; + /* 44 */ le_uint16_t hair_g = 0; + /* 46 */ le_uint16_t hair_b = 0; + /* 48 */ le_float proportion_x = 0.0; + /* 4C */ le_float proportion_y = 0.0; /* 50 */ PlayerVisualConfig() noexcept; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 87e9f2c5..d497430a 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1419,7 +1419,6 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) cmd.variations.clear(0); cmd.client_id = c->lobby_client_id; - cmd.leader_id = l->leader_id; cmd.event = l->event; cmd.section_id = l->section_id; cmd.rare_seed = l->random_seed; @@ -1428,6 +1427,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) uint8_t player_count = 0; auto watched_lobby = l->watched_lobby.lock(); if (watched_lobby) { + cmd.leader_id = watched_lobby->leader_id; // Live spectating for (size_t z = 0; z < 4; z++) { if (!watched_lobby->clients[z]) { @@ -1466,6 +1466,10 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) if (ev->type != Episode3::BattleRecord::Event::Type::SET_INITIAL_PLAYERS) { throw runtime_error("battle record does not begin with set players event"); } + if (ev->players.empty()) { + throw runtime_error("battle record contains no players"); + } + cmd.leader_id = ev->players[0].lobby_data.client_id; for (const auto& entry : ev->players) { uint8_t client_id = entry.lobby_data.client_id; if (client_id >= 4) {