handle incorrect flags in 10 command

This commit is contained in:
Martin Michelsen
2025-09-14 12:49:21 -07:00
parent 40a6f49b29
commit fb783034bc
3 changed files with 52 additions and 48 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
0019 = P2 Scientist after defeating dragon 0019 = P2 Scientist after defeating dragon
001E = Entered Caves 1 (Gov 2-1) 001E = Entered Caves 1 (Gov 2-1)
001F = Entered De Rol Le in 2-4 001F = Entered De Rol Le in 2-4
0020 = De Ro lee defeated 0020 = De Rol Le defeated
0021 = Mines unlocked (P2 Tyrell after defeating De Rol Le) 0021 = Mines unlocked (P2 Tyrell after defeating De Rol Le)
0028 = Entered Mines 1 0028 = Entered Mines 1
0029 = Entered Vol Opt Area 0029 = Entered Vol Opt Area
+20 -9
View File
@@ -674,15 +674,26 @@ struct S_LegacyJoinGame_XB_0E {
// 10 (C->S): Menu selection // 10 (C->S): Menu selection
// Internal name: SndAction // Internal name: SndAction
// header.flag is a bit field containing two flags: 02 specifies if a password // header.flag has different meanings depending on which menu is selected.
// is present, and 01 specifies if a name is present. (If both are set, the // For most menus, header.flag is a bit field containing two flags: 02
// name comes first, as described below). These two bits directly correspond to // specifies if a password is present, and 01 specifies if a name is present.
// the two lowest bits in the flags field of the game menu: 02 specifies that // (If both are set, the name comes first, as described below). These two bits
// the game is locked, but the function of 01 is unknown. The ability to send // directly correspond to the two lowest bits in the flags field of the game
// a name along with a menu choice is unused in all client versions except // menu: 02 specifies that the game is locked, but the function of 01 is
// Episode 3, where it's used in the tourname entries menu. It's not clear why // unknown. The ability to send a name along with a menu choice is unused in
// all other versions have the ability send a name here - it may be a relic // all client versions except Episode 3, where it's used in the tournament
// from very early development. // 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)
struct C_MenuSelectionBase_10 { struct C_MenuSelectionBase_10 {
le_uint32_t menu_id = 0; le_uint32_t menu_id = 0;
+31 -38
View File
@@ -2978,83 +2978,76 @@ template <bool UsesUTF16>
static asio::awaitable<void> on_10(shared_ptr<Client> c, Channel::Message& msg) { static asio::awaitable<void> on_10(shared_ptr<Client> c, Channel::Message& msg) {
constexpr TextEncoding Encoding = UsesUTF16 ? TextEncoding::UTF16 : TextEncoding::MARKED; constexpr TextEncoding Encoding = UsesUTF16 ? TextEncoding::UTF16 : TextEncoding::MARKED;
uint32_t menu_id; const auto& base_cmd = check_size_t<C_MenuSelectionBase_10>(msg.data, 0xFFFF);
uint32_t item_id;
string name; string name;
string password; string password;
switch (msg.flag) { switch (msg.data.size()) {
case 0: { case sizeof(C_MenuSelectionBase_10):
const auto& cmd = check_size_t<C_MenuSelectionBase_10>(msg.data); break;
menu_id = cmd.menu_id; case sizeof(C_MenuSelectionWithNameT_10<Encoding>): {
item_id = cmd.item_id; static_assert(
sizeof(C_MenuSelectionWithNameT_10<Encoding>) == sizeof(C_MenuSelectionWithPasswordT_10<Encoding>),
"Single-flag 10 commands should be the same size");
if (msg.flag & 1) {
const auto& cmd = check_size_t<C_MenuSelectionWithNameT_10<Encoding>>(msg.data);
name = cmd.name.decode(c->language());
} else if (msg.flag & 2) {
const auto& cmd = check_size_t<C_MenuSelectionWithPasswordT_10<Encoding>>(msg.data);
password = cmd.password.decode(c->language());
}
break; break;
} }
case 1: { case sizeof(C_MenuSelectionWithNameAndPasswordT_10<Encoding>): {
const auto& cmd = check_size_t<C_MenuSelectionWithNameT_10<Encoding>>(msg.data);
name = cmd.name.decode(c->language());
menu_id = cmd.menu_id;
item_id = cmd.item_id;
break;
}
case 2: {
const auto& cmd = check_size_t<C_MenuSelectionWithPasswordT_10<Encoding>>(msg.data);
password = cmd.password.decode(c->language());
menu_id = cmd.menu_id;
item_id = cmd.item_id;
break;
}
case 3: {
const auto& cmd = check_size_t<C_MenuSelectionWithNameAndPasswordT_10<Encoding>>(msg.data); const auto& cmd = check_size_t<C_MenuSelectionWithNameAndPasswordT_10<Encoding>>(msg.data);
name = cmd.name.decode(c->language()); name = cmd.name.decode(c->language());
password = cmd.password.decode(c->language()); password = cmd.password.decode(c->language());
menu_id = cmd.menu_id;
item_id = cmd.item_id;
break; break;
} }
default: default:
throw std::runtime_error("invalid options for menu selection command"); throw runtime_error("unknown menu selection format");
} }
auto s = c->require_server_state(); auto s = c->require_server_state();
switch (menu_id) { switch (base_cmd.menu_id) {
case MenuID::MAIN: case MenuID::MAIN:
co_await on_10_main_menu(c, item_id); co_await on_10_main_menu(c, base_cmd.item_id);
break; break;
case MenuID::CLEAR_LICENSE_CONFIRMATION: case MenuID::CLEAR_LICENSE_CONFIRMATION:
co_await on_10_clear_license_confirmation(c, item_id); co_await on_10_clear_license_confirmation(c, base_cmd.item_id);
break; break;
case MenuID::INFORMATION: case MenuID::INFORMATION:
co_await on_10_information(c, item_id); co_await on_10_information(c, base_cmd.item_id);
break; break;
case MenuID::PROXY_OPTIONS: case MenuID::PROXY_OPTIONS:
co_await on_10_proxy_options(c, item_id); co_await on_10_proxy_options(c, base_cmd.item_id);
break; break;
case MenuID::PROXY_DESTINATIONS: case MenuID::PROXY_DESTINATIONS:
co_await on_10_proxy_destinations(c, item_id); co_await on_10_proxy_destinations(c, base_cmd.item_id);
break; break;
case MenuID::GAME: case MenuID::GAME:
co_await on_10_game_menu(c, item_id, std::move(password)); co_await on_10_game_menu(c, base_cmd.item_id, std::move(password));
break; break;
case MenuID::QUEST_CATEGORIES_EP1: case MenuID::QUEST_CATEGORIES_EP1:
case MenuID::QUEST_CATEGORIES_EP2: case MenuID::QUEST_CATEGORIES_EP2:
co_await on_10_quest_categories(c, item_id); co_await on_10_quest_categories(c, base_cmd.item_id);
break; break;
case MenuID::QUEST_EP1: case MenuID::QUEST_EP1:
case MenuID::QUEST_EP2: case MenuID::QUEST_EP2:
co_await on_10_quest_menu(c, item_id); co_await on_10_quest_menu(c, base_cmd.item_id);
break; break;
case MenuID::PATCH_SWITCHES: case MenuID::PATCH_SWITCHES:
co_await on_10_patch_switches(c, item_id); co_await on_10_patch_switches(c, base_cmd.item_id);
break; break;
case MenuID::PROGRAMS: case MenuID::PROGRAMS:
co_await on_10_programs(c, item_id); co_await on_10_programs(c, base_cmd.item_id);
break; break;
case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS_FOR_SPEC:
case MenuID::TOURNAMENTS: case MenuID::TOURNAMENTS:
co_await on_10_tournaments(c, menu_id, item_id); co_await on_10_tournaments(c, base_cmd.menu_id, base_cmd.item_id);
break; break;
case MenuID::TOURNAMENT_ENTRIES: case MenuID::TOURNAMENT_ENTRIES:
co_await on_10_tournament_entries(c, item_id, std::move(name), std::move(password)); co_await on_10_tournament_entries(c, base_cmd.item_id, std::move(name), std::move(password));
break; break;
default: default:
send_message_box(c, "Incorrect menu ID"); send_message_box(c, "Incorrect menu ID");