diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index d43f3494..77c5d0ab 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -65,13 +65,13 @@ static void check_is_game(shared_ptr l, bool is_game) { } static void check_is_ep3(shared_ptr c, bool is_ep3) { - if (!!(c->flags & Client::Flag::IS_EPISODE_3) != is_ep3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3) != is_ep3) { throw precondition_failed(is_ep3 ? "$C6This command can only\nbe used in Episode 3." : "$C6This command cannot\nbe used in Episode 3."); } } static void check_cheats_enabled(shared_ptr l) { - if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { + if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED)) { throw precondition_failed("$C6This command can\nonly be used in\ncheat mode."); } } @@ -114,7 +114,7 @@ static void server_command_lobby_info(shared_ptr c, const std::string&) } lines.emplace_back(string_printf("$C7Section ID: $C6%s$C7", name_for_section_id(l->section_id).c_str())); - if (l->flags & Lobby::Flag::DROPS_ENABLED) { + if (l->check_flag(Lobby::Flag::DROPS_ENABLED)) { if (l->item_creator) { lines.emplace_back("Server item table"); } else { @@ -123,7 +123,7 @@ static void server_command_lobby_info(shared_ptr c, const std::string&) } else { lines.emplace_back("No item drops"); } - if (l->flags & Lobby::Flag::CHEATS_ENABLED) { + if (l->check_flag(Lobby::Flag::CHEATS_ENABLED)) { lines.emplace_back("Cheats enabled"); } @@ -186,13 +186,13 @@ static void proxy_command_lobby_info(shared_ptr ses, } vector cheats_tokens; - if (ses->options.switch_assist) { + if (ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) { cheats_tokens.emplace_back("SWA"); } - if (ses->options.infinite_hp) { + if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { cheats_tokens.emplace_back("HP"); } - if (ses->options.infinite_tp) { + if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) { cheats_tokens.emplace_back("TP"); } if (!cheats_tokens.empty()) { @@ -201,13 +201,13 @@ static void proxy_command_lobby_info(shared_ptr ses, } vector behaviors_tokens; - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { behaviors_tokens.emplace_back("SAVE"); } - if (ses->options.suppress_remote_login) { + if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) { behaviors_tokens.emplace_back("SL"); } - if (ses->options.function_call_return_value >= 0) { + if (ses->config.check_flag(Client::Flag::PROXY_BLOCK_FUNCTION_CALLS)) { behaviors_tokens.emplace_back("BFC"); } if (!behaviors_tokens.empty()) { @@ -215,9 +215,9 @@ static void proxy_command_lobby_info(shared_ptr ses, msg += join(behaviors_tokens, ","); } - if (ses->options.override_section_id >= 0) { + if (ses->config.override_section_id != 0xFF) { msg += "\n$C7SecID*: $C6"; - msg += name_for_section_id(ses->options.override_section_id); + msg += name_for_section_id(ses->config.override_section_id); } send_text_message(ses->client_channel, msg); @@ -248,9 +248,8 @@ static void proxy_command_arrow(shared_ptr ses, cons static void server_command_debug(shared_ptr c, const std::string&) { check_license_flags(c, License::Flag::DEBUG); - c->options.debug = !c->options.debug; - send_text_message_printf(c, "Debug %s", - c->options.debug ? "enabled" : "disabled"); + c->config.toggle_flag(Client::Flag::DEBUG_ENABLED); + send_text_message_printf(c, "Debug %s", (c->config.check_flag(Client::Flag::DEBUG_ENABLED) ? "enabled" : "disabled")); } static void server_command_show_material_counts(shared_ptr c, const std::string&) { @@ -293,7 +292,7 @@ static void server_command_patch(shared_ptr c, const std::string& args) // Note: We can't look this up outside of the closure because // c->specific_version can change during prepare_client_for_patches auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at( - string_printf("%s-%08" PRIX32, args.c_str(), c->specific_version)); + string_printf("%s-%08" PRIX32, args.c_str(), c->config.specific_version)); send_function_call(c, fn); c->function_call_response_queue.emplace_back(empty_function_call_response_handler); } catch (const out_of_range&) { @@ -307,15 +306,14 @@ static void empty_patch_return_handler(uint32_t, uint32_t) {} static void proxy_command_patch(shared_ptr ses, const std::string& args) { auto send_call = [args, ses](uint32_t specific_version, uint32_t) { try { - if (ses->newserv_client_config.cfg.specific_version != specific_version) { - ses->newserv_client_config.cfg.specific_version = specific_version; - ses->log.info("Version detected as %08" PRIX32, ses->newserv_client_config.cfg.specific_version); + if (ses->config.specific_version != specific_version) { + ses->config.specific_version = specific_version; + ses->log.info("Version detected as %08" PRIX32, ses->config.specific_version); } auto s = ses->require_server_state(); auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at( - string_printf("%s-%08" PRIX32, args.c_str(), ses->newserv_client_config.cfg.specific_version)); - send_function_call( - ses->client_channel, ses->newserv_client_config.cfg.flags, fn); + string_printf("%s-%08" PRIX32, args.c_str(), ses->config.specific_version)); + send_function_call(ses->client_channel, ses->config, fn); // Don't forward the patch response to the server ses->function_call_return_handler_queue.emplace_back(empty_patch_return_handler); } catch (const out_of_range&) { @@ -325,28 +323,28 @@ static void proxy_command_patch(shared_ptr ses, cons auto send_version_detect_or_send_call = [args, ses, send_call]() { if (ses->version() == GameVersion::GC && - ses->newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + ses->config.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { auto s = ses->require_server_state(); send_function_call( ses->client_channel, - ses->newserv_client_config.cfg.flags, + ses->config, s->function_code_index->name_to_function.at("VersionDetect")); ses->function_call_return_handler_queue.emplace_back(send_call); } else { - send_call(ses->newserv_client_config.cfg.specific_version, 0); + send_call(ses->config.specific_version, 0); } }; // This mirrors the implementation in prepare_client_for_patches - if (!(ses->newserv_client_config.cfg.flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { + if (!ses->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { auto s = ses->require_server_state(); send_function_call( - ses->client_channel, ses->newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2734EC); + ses->client_channel, ses->config, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2734EC); ses->function_call_return_handler_queue.emplace_back([s, ses, send_version_detect_or_send_call](uint32_t, uint32_t) -> void { send_function_call( - ses->client_channel, ses->newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); + ses->client_channel, ses->config, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); ses->function_call_return_handler_queue.emplace_back([ses, send_version_detect_or_send_call](uint32_t, uint32_t) -> void { - ses->newserv_client_config.cfg.flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; + ses->config.set_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); send_version_detect_or_send_call(); }); }); @@ -358,21 +356,21 @@ static void proxy_command_patch(shared_ptr ses, cons static void server_command_persist(shared_ptr c, const std::string&) { check_license_flags(c, License::Flag::DEBUG); auto l = c->require_lobby(); - if (l->flags & Lobby::Flag::DEFAULT) { + if (l->check_flag(Lobby::Flag::DEFAULT)) { send_text_message(c, "$C6Default lobbies\ncannot be marked\ntemporary"); } else { - l->flags ^= Lobby::Flag::PERSISTENT; + l->toggle_flag(Lobby::Flag::PERSISTENT); send_text_message_printf(c, "Lobby persistence\n%s", - (l->flags & Lobby::Flag::PERSISTENT) ? "enabled" : "disabled"); + l->check_flag(Lobby::Flag::PERSISTENT) ? "enabled" : "disabled"); } } static void server_command_exit(shared_ptr c, const std::string&) { auto l = c->require_lobby(); if (l->is_game()) { - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { c->channel.send(0xED, 0x00); - } else if (l->flags & (Lobby::Flag::QUEST_IN_PROGRESS | Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { + } else if (l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; c->channel.send(0x60, 0x00, cmd); c->area = 0; @@ -381,7 +379,7 @@ static void server_command_exit(shared_ptr c, const std::string&) { } } else { send_self_leave_notification(c); - if (!(c->flags & Client::Flag::NO_D6)) { + if (!c->config.check_flag(Client::Flag::NO_D6)) { send_message_box(c, ""); } @@ -393,7 +391,7 @@ static void server_command_exit(shared_ptr c, const std::string&) { static void proxy_command_exit(shared_ptr ses, const std::string&) { if (ses->is_in_game) { - if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { + if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { ses->client_channel.send(0xED, 0x00); } else if (ses->is_in_quest) { G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; @@ -477,11 +475,11 @@ static void server_command_cheat(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - if (l->flags & Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE) { + if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE)) { send_text_message(c, "$C6Cheat mode cannot\nbe changed on this\nserver"); } else { - l->flags ^= Lobby::Flag::CHEATS_ENABLED; - send_text_message_printf(l, "Cheat mode %s", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); + l->toggle_flag(Lobby::Flag::CHEATS_ENABLED); + send_text_message_printf(l, "Cheat mode %s", l->check_flag(Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); } } @@ -502,18 +500,18 @@ static void server_command_lobby_event(shared_ptr c, const std::string& static void proxy_command_lobby_event(shared_ptr ses, const std::string& args) { if (args.empty()) { - ses->options.override_lobby_event = -1; + ses->config.override_lobby_event = 0xFF; } else { uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { send_text_message(ses->client_channel, "$C6No such lobby event."); } else { - ses->options.override_lobby_event = new_event; + ses->config.override_lobby_event = new_event; // This command is supported on all V3 versions except Ep1&2 Trial - if ((ses->version() == GameVersion::GC && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || + if ((ses->version() == GameVersion::GC && !ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) || (ses->version() == GameVersion::XB) || (ses->version() == GameVersion::BB)) { - ses->client_channel.send(0xDA, ses->options.override_lobby_event); + ses->client_channel.send(0xDA, ses->config.override_lobby_event); } } } @@ -530,7 +528,7 @@ static void server_command_lobby_event_all(shared_ptr c, const std::stri auto s = c->require_server_state(); for (auto l : s->all_lobbies()) { - if (l->is_game() || !(l->flags & Lobby::Flag::DEFAULT)) { + if (l->is_game() || !l->check_flag(Lobby::Flag::DEFAULT)) { continue; } @@ -543,26 +541,33 @@ static void server_command_lobby_type(shared_ptr c, const std::string& a auto l = c->require_lobby(); check_is_game(l, false); - uint8_t new_type = args.empty() ? 0 : lobby_type_for_name(args); - if (new_type == 0x80) { - send_text_message(c, "$C6No such lobby type"); - return; + uint8_t new_type; + if (args.empty()) { + new_type = 0x80; + } else { + new_type = lobby_type_for_name(args); + if (new_type == 0x80) { + send_text_message(c, "$C6No such lobby type"); + return; + } } - uint8_t max_standard_type = ((c->flags & Client::Flag::IS_EPISODE_3) ? 20 : 15); - c->options.override_lobby_number = (new_type < max_standard_type) ? -1 : new_type; + c->config.override_lobby_number = new_type; send_join_lobby(c, l); } static void proxy_command_lobby_type(shared_ptr ses, const std::string& args) { - uint8_t new_type = args.empty() ? 0 : lobby_type_for_name(args); - if (new_type == 0x80) { - send_text_message(ses->client_channel, "$C6No such lobby type"); - return; + uint8_t new_type; + if (args.empty()) { + new_type = 0x80; + } else { + new_type = lobby_type_for_name(args); + if (new_type == 0x80) { + send_text_message(ses->client_channel, "$C6No such lobby type"); + return; + } } - - uint8_t max_standard_type = ((ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) ? 20 : 15); - ses->options.override_lobby_number = (new_type < max_standard_type) ? -1 : new_type; + ses->config.override_lobby_number = new_type; } static string file_path_for_recording(const std::string& args, uint32_t serial_number) { @@ -587,7 +592,7 @@ static void server_command_saverec(shared_ptr c, const std::string& args } static void server_command_playrec(shared_ptr c, const std::string& args) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_text_message(c, "$C4This command can\nonly be used on\nEpisode 3"); return; } @@ -599,10 +604,9 @@ static void server_command_playrec(shared_ptr c, const std::string& args string file_path = file_path_for_recording(args, c->license->serial_number); auto s = c->require_server_state(); - uint32_t flags = Lobby::Flag::IS_SPECTATOR_TEAM; string filename = args; - if (filename[0] == '!') { - flags |= Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY; + bool start_battle_player_immediately = (filename[0] == '!'); + if (start_battle_player_immediately) { filename = filename.substr(1); } @@ -617,10 +621,13 @@ static void server_command_playrec(shared_ptr c, const std::string& args shared_ptr battle_player( new Episode3::BattleRecordPlayer(record, s->game_server->get_base())); auto game = create_game_generic( - s, c, args, "", Episode::EP3, GameMode::NORMAL, 0, flags, false, nullptr, battle_player); + s, c, args, "", Episode::EP3, GameMode::NORMAL, 0, false, nullptr, battle_player); if (game) { + if (start_battle_player_immediately) { + game->set_flag(Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY); + } s->change_client_lobby(c, game); - c->flags |= Client::Flag::LOADING; + c->config.set_flag(Client::Flag::LOADING); } } else { send_text_message(c, "$C4This command cannot\nbe used in a game"); @@ -629,7 +636,7 @@ static void server_command_playrec(shared_ptr c, const std::string& args static void server_command_meseta(shared_ptr c, const std::string& args) { check_is_ep3(c, true); - if (!c->options.debug) { + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); return; } @@ -651,14 +658,14 @@ static void server_command_secid(shared_ptr c, const std::string& args) check_cheats_allowed(c->require_server_state()); if (!args[0]) { - c->options.override_section_id = -1; + c->config.override_section_id = 0xFF; send_text_message(c, "$C6Override section ID\nremoved"); } else { uint8_t new_secid = section_id_for_name(args); if (new_secid == 0xFF) { send_text_message(c, "$C6Invalid section ID"); } else { - c->options.override_section_id = new_secid; + c->config.override_section_id = new_secid; string name = name_for_section_id(new_secid); send_text_message_printf(c, "$C6Override section ID\nset to %s", name.c_str()); } @@ -668,14 +675,14 @@ static void server_command_secid(shared_ptr c, const std::string& args) static void proxy_command_secid(shared_ptr ses, const std::string& args) { check_cheats_allowed(ses->require_server_state()); if (!args[0]) { - ses->options.override_section_id = -1; + ses->config.override_section_id = 0xFF; send_text_message(ses->client_channel, "$C6Override section ID\nremoved"); } else { uint8_t new_secid = section_id_for_name(args); if (new_secid == 0xFF) { send_text_message(ses->client_channel, "$C6Invalid section ID"); } else { - ses->options.override_section_id = new_secid; + ses->config.override_section_id = new_secid; string name = name_for_section_id(new_secid); send_text_message(ses->client_channel, "$C6Override section ID\nset to " + name); } @@ -689,10 +696,12 @@ static void server_command_rand(shared_ptr c, const std::string& args) { check_cheats_allowed(s); if (!args[0]) { - c->options.override_random_seed = -1; + c->config.clear_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED); + c->config.override_random_seed = 0; send_text_message(c, "$C6Override seed\nremoved"); } else { - c->options.override_random_seed = stoul(args, 0, 16); + c->config.set_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED); + c->config.override_random_seed = stoul(args, 0, 16); send_text_message(c, "$C6Override seed\nset"); } } @@ -700,10 +709,12 @@ static void server_command_rand(shared_ptr c, const std::string& args) { static void proxy_command_rand(shared_ptr ses, const std::string& args) { check_proxy_cheats_allowed(ses->require_server_state()); if (!args[0]) { - ses->options.override_random_seed = -1; + ses->config.clear_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED); + ses->config.override_random_seed = 0; send_text_message(ses->client_channel, "$C6Override seed\nremoved"); } else { - ses->options.override_random_seed = stoul(args, 0, 16); + ses->config.set_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED); + ses->config.override_random_seed = stoul(args, 0, 16); send_text_message(ses->client_channel, "$C6Override seed\nset"); } } @@ -738,16 +749,16 @@ static void server_command_toggle_spectator_flag(shared_ptr c, const std check_is_leader(l, c); } - if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { + if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { send_text_message(c, "$C6This command cannot\nbe used in a spectator\nteam"); } - if (l->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) { - l->flags &= ~Lobby::Flag::SPECTATORS_FORBIDDEN; + if (l->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) { + l->clear_flag(Lobby::Flag::SPECTATORS_FORBIDDEN); send_text_message(l, "$C6Spectators allowed"); } else { - l->flags |= Lobby::Flag::SPECTATORS_FORBIDDEN; + l->set_flag(Lobby::Flag::SPECTATORS_FORBIDDEN); for (auto watcher_l : l->watcher_lobbies) { send_command(watcher_l, 0xED, 0x00); } @@ -1127,7 +1138,7 @@ static void server_command_what(shared_ptr c, const std::string&) { if (!episode_has_arpg_semantics(l->episode)) { return; } - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { send_text_message(c, "$C4Item tracking is\nnot available"); } else { float min_dist2 = 0.0f; @@ -1178,15 +1189,16 @@ static void server_command_infinite_hp(shared_ptr c, const std::string&) check_is_game(l, true); check_cheats_enabled(l); - c->options.infinite_hp = !c->options.infinite_hp; - send_text_message_printf(c, "$C6Infinite HP %s", c->options.infinite_hp ? "enabled" : "disabled"); + c->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED); + send_text_message_printf(c, "$C6Infinite HP %s", c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED) ? "enabled" : "disabled"); } static void proxy_command_infinite_hp(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_proxy_cheats_allowed(s); - ses->options.infinite_hp = !ses->options.infinite_hp; - send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", ses->options.infinite_hp ? "enabled" : "disabled"); + ses->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED); + send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", + ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED) ? "enabled" : "disabled"); } static void server_command_infinite_tp(shared_ptr c, const std::string&) { @@ -1195,17 +1207,16 @@ static void server_command_infinite_tp(shared_ptr c, const std::string&) check_is_game(l, true); check_cheats_enabled(l); - c->options.infinite_tp = !c->options.infinite_tp; - send_text_message_printf(c, "$C6Infinite TP %s", - c->options.infinite_tp ? "enabled" : "disabled"); + c->config.toggle_flag(Client::Flag::INFINITE_TP_ENABLED); + send_text_message_printf(c, "$C6Infinite TP %s", c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED) ? "enabled" : "disabled"); } static void proxy_command_infinite_tp(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_proxy_cheats_allowed(s); - ses->options.infinite_tp = !ses->options.infinite_tp; + ses->config.toggle_flag(Client::Flag::INFINITE_TP_ENABLED); send_text_message_printf(ses->client_channel, "$C6Infinite TP %s", - ses->options.infinite_tp ? "enabled" : "disabled"); + ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED) ? "enabled" : "disabled"); } static void server_command_switch_assist(shared_ptr c, const std::string&) { @@ -1214,28 +1225,28 @@ static void server_command_switch_assist(shared_ptr c, const std::string check_is_game(l, true); check_cheats_enabled(l); - c->options.switch_assist = !c->options.switch_assist; + c->config.toggle_flag(Client::Flag::SWITCH_ASSIST_ENABLED); send_text_message_printf(c, "$C6Switch assist %s", - c->options.switch_assist ? "enabled" : "disabled"); + c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? "enabled" : "disabled"); } static void proxy_command_switch_assist(shared_ptr ses, const std::string&) { auto s = ses->require_server_state(); check_proxy_cheats_allowed(s); - ses->options.switch_assist = !ses->options.switch_assist; + ses->config.toggle_flag(Client::Flag::SWITCH_ASSIST_ENABLED); send_text_message_printf(ses->client_channel, "$C6Switch assist %s", - ses->options.switch_assist ? "enabled" : "disabled"); + ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) ? "enabled" : "disabled"); } static void server_command_drop(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - if (l->flags & Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED) { + if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED)) { send_text_message(c, "Drop mode cannot\nbe changed on this\nserver"); } else { - l->flags ^= Lobby::Flag::DROPS_ENABLED; - send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); + l->toggle_flag(Lobby::Flag::DROPS_ENABLED); + send_text_message_printf(l, "Drops %s", l->check_flag(Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); } } @@ -1244,11 +1255,11 @@ static void server_command_itemtable(shared_ptr c, const std::string&) { auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); - if (l->flags & Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE) { + if (l->check_flag(Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE)) { send_text_message(c, "Cannot switch item\ntables on this\nserver"); } else if (l->base_version == GameVersion::BB) { send_text_message(c, "Cannot use client\nitem table on BB"); - } else if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + } else if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { send_text_message(c, "Cannot use server\nitem tables if item\ntracking is off"); } else if (l->item_creator) { l->item_creator.reset(); @@ -1312,7 +1323,7 @@ static void proxy_command_item(shared_ptr ses, const } static void server_command_enable_ep3_battle_debug_menu(shared_ptr c, const std::string& args) { - if (!c->options.debug) { + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); return; } diff --git a/src/Client.cc b/src/Client.cc index 03646084..cb037576 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -17,31 +17,119 @@ using namespace std; -const uint64_t CLIENT_CONFIG_MAGIC = 0x492A890E82AC9839; +const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32; static atomic next_id(1); -ClientOptions::ClientOptions() - : switch_assist(false), - infinite_hp(false), - infinite_tp(false), - debug(false), - override_section_id(-1), - override_lobby_event(-1), - override_lobby_number(-1), - override_random_seed(-1), - save_files(false), - enable_chat_commands(true), - enable_chat_filter(true), - enable_player_notifications(false), - suppress_client_pings(false), - suppress_remote_login(false), - zero_remote_guild_card(false), - ep3_infinite_meseta(false), - ep3_infinite_time(false), - red_name(false), - blank_name(false), - function_call_return_value(-1) {} +void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_version) { + this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED); + this->set_flag(Flag::PROXY_CHAT_FILTER_ENABLED); + + switch (sub_version) { + case -1: // Initial check (before sub_version recognition) + switch (version) { + case GameVersion::DC: + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case GameVersion::GC: + break; + case GameVersion::XB: + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case GameVersion::PC: + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case GameVersion::PATCH: + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + break; + case GameVersion::BB: + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::SAVE_ENABLED); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + default: + throw logic_error("invalid game version"); + } + break; + + case 0x20: // DCNTE, possibly also DCv1 JP + case 0x21: // DCv1 US + this->set_flag(Flag::IS_DC_V1); + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + // In the case of DCNTE, the IS_DC_TRIAL_EDITION flag is already set when + // we get here + break; + case 0x23: // DCv1 EU + this->set_flag(Flag::IS_DC_V1); + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + break; + case 0x25: // DCv2 JP + case 0x26: // DCv2 US + case 0x28: // DCv2 EU + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case 0x29: // PC + this->set_flag(Flag::NO_D6); + this->set_flag(Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 JP v1.02, at least one version of PSO XB + case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00 + case 0x34: // GC Ep1&2 JP v1.03 + // In the case of GC Trial Edition, the IS_GC_TRIAL_EDITION flag is + // already set when we get here (because the client has used V2 encryption + // instead of V3) + break; + case 0x32: // GC Ep1&2 EU 50Hz + case 0x33: // GC Ep1&2 EU 60Hz + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + break; + case 0x35: // GC Ep1&2 JP v1.04 (Plus) + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case 0x36: // GC Ep1&2 US v1.02 (Plus) + case 0x39: // GC Ep1&2 JP v1.05 (Plus) + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + break; + case 0x40: // GC Ep3 JP and Trial Edition + // sub_version can't be used to tell JP final and Trial Edition apart; we + // instead look at header.flag in the 61 command. + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::IS_EPISODE_3); + this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case 0x42: // Also GC Ep3 JP? + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::IS_EPISODE_3); + this->set_flag(Flag::ENCRYPTED_SEND_FUNCTION_CALL); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case 0x41: // GC Ep3 US + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::IS_EPISODE_3); + this->set_flag(Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL); + this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); + break; + case 0x43: // GC Ep3 EU + this->set_flag(Flag::NO_D6_AFTER_LOBBY); + this->set_flag(Flag::IS_EPISODE_3); + this->set_flag(Flag::NO_SEND_FUNCTION_CALL); + break; + default: + throw runtime_error(string_printf("unknown sub_version %" PRIX64, sub_version)); + } +} Client::Client( shared_ptr server, @@ -51,16 +139,12 @@ Client::Client( : server(server), id(next_id++), log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level), - bb_game_state(0), - flags(flags_for_version(version, -1)), - specific_version(default_specific_version_for_version(version, -1)), channel(bev, version, 1, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN), server_behavior(server_behavior), should_disconnect(false), should_send_to_lobby_server(false), should_send_to_proxy_server(false), - proxy_destination_address(0), - proxy_destination_port(0), + bb_connection_phase(0xFF), x(0.0f), z(0.0f), area(0), @@ -89,6 +173,10 @@ Client::Client( can_chat(true), pending_bb_save_player_index(0), dol_base_addr(0) { + + this->config.set_flags_for_version(version, -1); + this->config.specific_version = default_specific_version_for_version(version, -1); + this->last_switch_enabled_command.header.subcommand = 0; memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr)); @@ -122,9 +210,9 @@ void Client::reschedule_ping_and_timeout_events() { QuestScriptVersion Client::quest_version() const { switch (this->version()) { case GameVersion::DC: - if (this->flags & Flag::IS_DC_TRIAL_EDITION) { + if (this->config.check_flag(Flag::IS_DC_TRIAL_EDITION)) { return QuestScriptVersion::DC_NTE; - } else if (this->flags & Flag::IS_DC_V1) { + } else if (this->config.check_flag(Flag::IS_DC_V1)) { return QuestScriptVersion::DC_V1; } else { return QuestScriptVersion::DC_V2; @@ -132,9 +220,9 @@ QuestScriptVersion Client::quest_version() const { case GameVersion::PC: return QuestScriptVersion::PC_V2; case GameVersion::GC: - if (this->flags & Flag::IS_GC_TRIAL_EDITION) { + if (this->config.check_flag(Flag::IS_GC_TRIAL_EDITION)) { return QuestScriptVersion::GC_NTE; - } else if (this->flags & Flag::IS_EPISODE_3) { + } else if (this->config.check_flag(Flag::IS_EPISODE_3)) { return QuestScriptVersion::GC_EP3; } else { return QuestScriptVersion::GC_V3; @@ -172,42 +260,6 @@ shared_ptr Client::require_lobby() const { return l; } -ClientConfig Client::export_config() const { - ClientConfig cc; - cc.magic = CLIENT_CONFIG_MAGIC; - cc.flags = this->flags; - cc.specific_version = this->specific_version; - cc.proxy_destination_address = this->proxy_destination_address; - cc.proxy_destination_port = this->proxy_destination_port; - cc.unused.clear(0xFF); - return cc; -} - -ClientConfigBB Client::export_config_bb() const { - ClientConfigBB cc; - cc.cfg = this->export_config(); - cc.bb_game_state = this->bb_game_state; - cc.bb_player_index = this->game_data.bb_player_index; - cc.unused.clear(0xFF); - return cc; -} - -void Client::import_config(const ClientConfig& cc) { - if (cc.magic != CLIENT_CONFIG_MAGIC) { - throw invalid_argument("invalid client config"); - } - this->flags = cc.flags; - this->specific_version = cc.specific_version; - this->proxy_destination_address = cc.proxy_destination_address; - this->proxy_destination_port = cc.proxy_destination_port; -} - -void Client::import_config(const ClientConfigBB& cc) { - this->import_config(cc.cfg); - this->bb_game_state = cc.bb_game_state; - this->game_data.bb_player_index = cc.bb_player_index; -} - void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) { reinterpret_cast(ctx)->save_game_data(); } diff --git a/src/Client.hh b/src/Client.hh index c4a79001..58e3de1e 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -3,6 +3,7 @@ #include #include +#include #include "Channel.hh" #include "CommandFormats.hh" @@ -23,100 +24,126 @@ extern const uint64_t CLIENT_CONFIG_MAGIC; class Server; struct Lobby; -struct ClientOptions { - // Options used on both game and proxy server - bool switch_assist; - bool infinite_hp; - bool infinite_tp; - bool debug; - int16_t override_section_id; // -1 = no override - int16_t override_lobby_event; // -1 = no override - int16_t override_lobby_number; // -1 = no override - int64_t override_random_seed; - - // Options used only on proxy server - bool save_files; - bool enable_chat_commands; - bool enable_chat_filter; - bool enable_player_notifications; - bool suppress_client_pings; - bool suppress_remote_login; - bool zero_remote_guild_card; - bool ep3_infinite_meseta; - bool ep3_infinite_time; - bool red_name; - bool blank_name; - int64_t function_call_return_value; // -1 = don't block function calls - - ClientOptions(); -}; - struct Client : public std::enable_shared_from_this { - enum Flag { - // Client is DC Network Trial Edition, which is missing a lot of features - // and uses some different command numbers than any other version - IS_DC_TRIAL_EDITION = 0x00002000, - // A 90 01 command has been sent (which proto will send a 93 in response to, - // and actual DCv1 will send a 92) - CHECKED_FOR_DC_V1_PROTOTYPE = 0x00080000, - // Client is DC v1 prototype - IS_DC_V1_PROTOTYPE = 0x00040000, - // Client is DC v1 - IS_DC_V1 = 0x00000010, - // Client is GC Episodes 1&2 Trial Edition, which is much more like PC than - // actual GC Episodes 1&2 - it uses PC encryption and is missing most of the - // features added in Episodes 1&2 - IS_GC_TRIAL_EDITION = 0x00200000, - // Client is GC Episode 3 Trial Edition, which is fairly close to the final - // Episode 3 build, but is missing a few commands that we'll have to avoid - // sending - IS_EP3_TRIAL_EDITION = 0x00400000, - // For patch server clients, client is Blue Burst rather than PC - IS_BB_PATCH = 0x00000001, - // After joining a lobby, client will no longer send D6 commands when they - // close message boxes - NO_D6_AFTER_LOBBY = 0x00000002, - // Client has the above flag and has already joined a lobby, or is not GC - NO_D6 = 0x00000004, - // Client is Episode 3, should be able to see CARD lobbies, and should only - // be able to see/join games with the EPISODE_3_ONLY flag - IS_EPISODE_3 = 0x00000008, - // Client disconnects if it receives B2 (send_function_call) - NO_SEND_FUNCTION_CALL = 0x00000200, - // Client requires doubly-encrypted code section in send_function_call - ENCRYPTED_SEND_FUNCTION_CALL = 0x00000800, - // Client supports send_function_call but does not actually run the code - SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x00001000, - // Client supports send_function_call and clears its caches properly before - // calling the function (so we don't need to patch it) - SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x00020000, - // Client is vulnerable to a buffer overflow that we can use to enable - // send_function_call - USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x00008000, + enum class Flag : uint64_t { + // clang-format off - // Client is loading into a game - LOADING = 0x00000020, - // Client is loading a quest - LOADING_QUEST = 0x00000040, - // Client is loading a joinable quest that has already started - LOADING_RUNNING_QUEST = 0x00100000, - // Client is waiting for other players to join a tournament game - LOADING_TOURNAMENT = 0x00010000, - // Client is in the information menu (login server only) - IN_INFORMATION_MENU = 0x00000080, - // Client is at the welcome message (login server only) - AT_WELCOME_MESSAGE = 0x00000100, - // Client has already received a 97 (enable saves) command, so don't show - // the programs menu anymore - SAVE_ENABLED = 0x00000400, - // Client has received newserv's Episode 3 card definitions, so don't send - // them again - HAS_EP3_CARD_DEFS = 0x00004000, - // Client has received newserv's Episode 3 media updates, so don't send them - // again - HAS_EP3_MEDIA_UPDATES = 0x00800000, + // Version-related flags + IS_DC_TRIAL_EDITION = 0x0000000000000001, + CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002, + IS_DC_V1_PROTOTYPE = 0x0000000000000004, + IS_DC_V1 = 0x0000000000000008, + IS_GC_TRIAL_EDITION = 0x0000000000000010, + IS_EP3_TRIAL_EDITION = 0x0000000000000020, + IS_EPISODE_3 = 0x0000000000000040, + IS_BB_PATCH = 0x0000000000000080, + NO_D6_AFTER_LOBBY = 0x0000000000000100, + NO_D6 = 0x0000000000000200, - UNUSED_FLAG_BITS = 0xFF010000, + // Flags describing the behavior for send_function_call + NO_SEND_FUNCTION_CALL = 0x0000000000001000, + ENCRYPTED_SEND_FUNCTION_CALL = 0x0000000000002000, + SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x0000000000004000, + SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x0000000000008000, + USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x0000000000010000, + + // State flags + LOADING = 0x0000000000100000, + LOADING_QUEST = 0x0000000000200000, + LOADING_RUNNING_JOINABLE_QUEST = 0x0000000000400000, + LOADING_TOURNAMENT = 0x0000000000800000, + IN_INFORMATION_MENU = 0x0000000001000000, + AT_WELCOME_MESSAGE = 0x0000000002000000, + SAVE_ENABLED = 0x0000000004000000, + HAS_EP3_CARD_DEFS = 0x0000000008000000, + HAS_EP3_MEDIA_UPDATES = 0x0000000010000000, + USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000, + HAS_GUILD_CARD_NUMBER = 0x0000000040000000, + + // Cheat mode flags + SWITCH_ASSIST_ENABLED = 0x0000000100000000, + INFINITE_HP_ENABLED = 0x0000000200000000, + INFINITE_TP_ENABLED = 0x0000000400000000, + DEBUG_ENABLED = 0x0000000800000000, + + // Proxy option flags + PROXY_SAVE_FILES = 0x0000001000000000, + PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000, + PROXY_CHAT_FILTER_ENABLED = 0x0000004000000000, + PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000, + PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000, + PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000, + PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000, + PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000, + PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000, + PROXY_RED_NAME_ENABLED = 0x0000200000000000, + PROXY_BLANK_NAME_ENABLED = 0x0000400000000000, + PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000, + // clang-format on + }; + + struct Config { + uint64_t enabled_flags = 0; // Client::Flag enum + uint32_t specific_version = 0; + int32_t override_random_seed = 0; + uint8_t override_section_id = 0xFF; // FF = no override + uint8_t override_lobby_event = 0xFF; // FF = no override + uint8_t override_lobby_number = 0x80; // 80 = no override + uint32_t proxy_destination_address = 0; + uint16_t proxy_destination_port = 0; + + Config() = default; + + [[nodiscard]] inline bool check_flag(Flag flag) const { + return !!(this->enabled_flags & static_cast(flag)); + } + inline void set_flag(Flag flag) { + this->enabled_flags |= static_cast(flag); + } + inline void clear_flag(Flag flag) { + this->enabled_flags &= (~static_cast(flag)); + } + inline void toggle_flag(Flag flag) { + this->enabled_flags ^= static_cast(flag); + } + + void set_flags_for_version(GameVersion version, int64_t sub_version); + + template + void parse_from(const parray& data) { + StringReader r(data.data(), data.size()); + if (r.get_u32l() != CLIENT_CONFIG_MAGIC) { + throw std::invalid_argument("config signature is incorrect"); + } + this->specific_version = r.get_u32l(); + this->enabled_flags = r.get_u64l(); + this->override_random_seed = r.get_u32l(); + this->proxy_destination_address = r.get_u32b(); + this->proxy_destination_port = r.get_u16l(); + this->override_section_id = r.get_u8(); + this->override_lobby_event = r.get_u8(); + this->override_lobby_number = r.get_u8(); + } + + template + void serialize_into(parray& data) const { + StringWriter w; + w.put_u32l(CLIENT_CONFIG_MAGIC); + w.put_u32l(this->specific_version); + w.put_u64l(this->enabled_flags); + w.put_u32l(this->override_random_seed); + w.put_u32b(this->proxy_destination_address); + w.put_u16l(this->proxy_destination_port); + w.put_u8(this->override_section_id); + w.put_u8(this->override_lobby_event); + w.put_u8(this->override_lobby_number); + + const auto& s = w.str(); + for (size_t z = 0; z < s.size(); z++) { + data[z] = s[z]; + } + data.clear_after(s.size(), 0xFF); + } }; std::weak_ptr server; @@ -127,13 +154,6 @@ struct Client : public std::enable_shared_from_this { // License & account std::shared_ptr license; - // Note: these fields are included in the client config. On GC, the client - // config can be up to 0x20 bytes; on BB it can be 0x28 bytes. We don't use - // all of that space. - uint8_t bb_game_state; - uint32_t flags; - uint32_t specific_version; - // Network Channel channel; struct sockaddr_storage next_connection_addr; @@ -141,15 +161,14 @@ struct Client : public std::enable_shared_from_this { bool should_disconnect; bool should_send_to_lobby_server; bool should_send_to_proxy_server; - uint32_t proxy_destination_address; - uint16_t proxy_destination_port; std::unordered_map> disconnect_hooks; + uint8_t bb_connection_phase; // Patch server std::vector patch_file_checksum_requests; // Lobby/positioning - ClientOptions options; + Config config; float x; float z; uint32_t area; @@ -203,11 +222,6 @@ struct Client : public std::enable_shared_from_this { std::shared_ptr require_server_state() const; std::shared_ptr require_lobby() const; - ClientConfig export_config() const; - ClientConfigBB export_config_bb() const; - void import_config(const ClientConfig& cc); - void import_config(const ClientConfigBB& cc); - static void dispatch_save_game_data(evutil_socket_t, short, void* ctx); void save_game_data(); static void dispatch_send_ping(evutil_socket_t, short, void* ctx); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 7b72579b..7ea102f1 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -88,44 +88,6 @@ // - - $r: Right arrow // - - $u: Up arrow -// This is the format of newserv's security data, which we call the client -// config. This data is opaque to the client, so this structure is not -// technically part of the PSO protocol. Because it is opaque to the client, we -// can use the server's native-endian types instead of being explicit as we do -// for all the other structs in this file. -enum ClientStateBB : uint8_t { - // Initial connection; server will redirect client to another port - INITIAL_LOGIN = 0x00, - // Second connection; server will send client game data and account data - DOWNLOAD_DATA = 0x01, - // Third connection; client will show the choose character menu - CHOOSE_PLAYER = 0x02, - // Fourth connection; used for saving characters only. If you do not create a - // character, the server sets this state during the third connection so this - // connection is effectively skipped. - SAVE_PLAYER = 0x03, - // Fifth connection; redirects client to login server - SHIP_SELECT = 0x04, - // All other connections - IN_GAME = 0x05, -}; - -struct ClientConfig { - uint64_t magic = 0; - uint32_t flags = 0; - uint32_t specific_version = 0; - uint32_t proxy_destination_address = 0; - uint16_t proxy_destination_port = 0; - parray unused; -} __packed__; - -struct ClientConfigBB { - ClientConfig cfg; - uint8_t bb_game_state = 0; - uint8_t bb_player_index = 0; - parray unused; -} __packed__; - //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // PATCH SERVER COMMANDS /////////////////////////////////////////////////////// @@ -478,8 +440,7 @@ struct C_LegacyLogin_BB_04 { // config set by the E6 command (and returned in the 93 command). In most cases, // E6 should be used for BB clients instead of 04. -template -struct S_UpdateClientConfig { +struct S_UpdateClientConfig_DC_PC_04 { // Note: What we call player_tag here is actually three fields: two uint8_ts // followed by a le_uint16_t. It's unknown what the uint8_t fields are for // (they seem to always be zero), but the le_uint16_t is likely a boolean @@ -489,16 +450,20 @@ struct S_UpdateClientConfig { // player is present and zero when none is present. le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - // The ClientConfig structure describes how newserv uses this command; other - // servers do not use the same format for the following 0x20 or 0x28 bytes (or - // may not use it at all). The cfg field is opaque to the client; it will send - // back the contents verbatim in its next 9E command (or on request via 9F). - ClientConfigT cfg; } __packed__; -struct S_UpdateClientConfig_DC_PC_V3_04 : S_UpdateClientConfig { +struct S_UpdateClientConfig_V3_04 { + le_uint32_t player_tag = 0x00010000; + le_uint32_t guild_card_number = 0; + // This field is opaque to the client; it will send back the contents verbatim + // in its next 9E command (or on request via 9F). + parray client_config; } __packed__; -struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig { + +struct S_UpdateClientConfig_BB_04 { + le_uint32_t player_tag = 0x00010000; + le_uint32_t guild_card_number = 0; + parray client_config; } __packed__; // 05: Disconnect @@ -530,7 +495,7 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig { // the chat string accidentally.) We call this byte private_flags in the places // where newserv uses it. -// 07 (S->C): Ship select menu +// 07 (S->C): Ship or block select menu // Internal name: RcvDirList // This command triggers a general form of blocking menu, which was used for // both the ship select and block select menus by Sega (and all other private @@ -539,6 +504,8 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig { // there was a separate command to send the block list, but it was scrapped. // Perhaps this was used for command A1, which is identical to 07 and A0 in all // versions of PSO (except DC NTE). +// The menu is titles "Ship Select" unless the first menu item begins with the +// text "BLOCK" (all caps), in which case it is titled "Block Select". // Command is a list of these; header.flag is the entry count. The first entry // is not included in the count and does not appear on the client. The text of @@ -860,17 +827,20 @@ struct S_ReconnectSplit_19 { // 21: GameGuard control (old versions of BB) // Format unknown -// 22: GameGuard check (BB) +// 0022: GameGuard check (BB) // Command 0022 is a 16-byte challenge (sent in the data field) using the // following structure. -struct SC_GameCardCheck_BB_0022 { +struct SC_GameGuardCheck_BB_0022 { parray data; } __packed__; -// Command 0122 uses a 4-byte challenge sent in the header.flag field instead. -// This version of the command has no other arguments. +// 0122 (C->S): Time deviation (BB) +// This command is sent when the client executes a quest opcode 5D (gettime) and +// the returned timestamp is before the previous timestamp returned, but not by +// too much - it seems the game only considers deltas between 3 seconds and 30 +// minutes suspicious for these purposes. // 23 (S->C): Momoka Item Exchange result (BB) // Sent in response to a 6xD9 command from the client. @@ -1693,7 +1663,18 @@ struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 { struct C_Login_BB_93 { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - pstring unused; + le_uint32_t sub_version = 0; + uint8_t language; + uint8_t character_slot; + // Values for connection_phase: + // 00 - initial connection (client will request system file, characters, etc.) + // 01 - choose character + // 02 - create character + // 03 - apply updates from dressing room + // 04 - login server + // 05 - lobby server (and beyond) + uint8_t connection_phase; + uint8_t client_code; le_uint32_t team_id = 0; pstring username; pstring password; @@ -1711,16 +1692,10 @@ struct C_Login_BB_93 { // the client config starts 8 bytes earlier on those versions and the entire // command is 8 bytes shorter, hence this odd-looking union. union VariableLengthSection { - union ClientConfigFields { - ClientConfigBB cfg; - pstring version_string; - parray as_u32; - } __packed__; - - ClientConfigFields old_clients_cfg; + parray old_client_config; struct NewFormat { parray hardware_info; - ClientConfigFields cfg; + parray client_config; } __packed__ new_clients; } __packed__ var; } __packed__; @@ -1909,11 +1884,7 @@ struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D { // header.flag is 1 if the client has UDP disabled. struct C_Login_GC_9E : C_Login_DC_PC_GC_9D { - union ClientConfigFields { - ClientConfig cfg; - parray data; - ClientConfigFields() : data() {} - } __packed__ client_config; + parray client_config; } __packed__; struct C_LoginExtended_GC_9E : C_Login_GC_9E { SC_MeetUserExtension extension; @@ -1951,10 +1922,16 @@ struct C_LoginExtended_BB_9E { // 9F (C->S): Client config / security data response (V3/BB) // The data is opaque to the client, as described at the top of this file. -// If newserv ever sent a 9F command (it currently does not), the response -// format here would be ClientConfig (0x20 bytes) on V3, or ClientConfigBB (0x28 -// bytes) on BB. However, on BB, this returns the client config that was set by -// a preceding 04 command, not the config set by a preceding E6 command. +// If newserv ever sent a 9F command (it currently does not). On BB, this +// command does not work during the data server phase. + +struct C_ClientConfig_V3_9F { + parray data; +} __packed__; + +struct C_ClientConfig_BB_9F { + parray data; +} __packed__; // A0 (C->S): Change ship // Internal name: SndShipList @@ -2806,7 +2783,7 @@ struct S_TournamentList_GC_Ep3_E0 { parray entries; } __packed__; -// E0 (C->S): Request team and key config (BB) +// E0 (C->S): Request system file (BB) // No arguments. The server should respond with an E1 or E2 command. // E1 (S->C): Game information (Episode 3) @@ -2830,10 +2807,10 @@ struct S_GameInformation_GC_Ep3_E1 { /* 0298 */ } __packed__; -// E1 (S->C): Team and key config missing? (BB) +// E1 (S->C): Create system file (BB) // This seems to take the place of 00E2 in certain cases. Perhaps it was used -// when a client hadn't logged in before and didn't have a team or key config, -// so the client should use appropriate defaults. +// when a client hadn't logged in before and didn't have a system file, so the +// client should use appropriate defaults. struct S_TeamAndKeyConfigMissing_00E1_BB { // If success is not equal to 1, the client shows a message saying "Forced @@ -3033,16 +3010,16 @@ struct C_JoinSpectatorTeam_GC_Ep3_E6_Flag01 { // Same format as 08 command. // E6 (S->C): Set guild card number and update client config (BB) -// BB clients have multiple client configs. This command sets the client config -// that is returned by the 93 commands, but does not affect the client config -// set by the 04 command (and returned in the 9E and 9F commands). +// This command sets the player's guild card number. During the data server +// phase, it also sets the client config and enabled features (these fields are +// ignored during the game server phase). struct S_ClientInit_BB_00E6 { - le_uint32_t error = 0; + le_uint32_t error_code = 0; le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; le_uint32_t team_id = 0; - ClientConfigBB cfg; + parray client_config; uint8_t can_create_team = 1; uint8_t episode_4_unlocked = 1; parray unused; @@ -3325,7 +3302,7 @@ struct C_PromoteTeamMember_BB_11EA { struct S_TeamMembershipInformation_BB_12EA { le_uint32_t unknown_a1 = 0; // Command is ignored unless this is 0 - le_uint32_t guild_card_number = 0; + le_uint32_t guild_card_number = 0; // Team membership ID? le_uint32_t team_id = 0; le_uint32_t unknown_a4 = 0; le_uint32_t privilege_level = 0; diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 2dd68912..226221bb 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -2349,7 +2349,7 @@ void Server::send_6xB6x41_to_all_clients() const { } if (map_commands_by_language[c->language()].empty()) { map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition( - this->last_chosen_map, c->language(), l->flags & Lobby::Flag::IS_EP3_TRIAL); + this->last_chosen_map, c->language(), l->check_flag(Lobby::Flag::IS_EP3_TRIAL)); } this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number); send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]); diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index 4a380798..2598f142 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -702,8 +702,8 @@ void Tournament::send_all_state_updates() const { // with this instance of the tournament - an intervening shell command // `reload ep3` could have changed the client's linkage if (c && - (c->flags & Client::Flag::IS_EPISODE_3) && - !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) && + c->config.check_flag(Client::Flag::IS_EPISODE_3) && + !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) && (c->ep3_tournament_team.lock() == team)) { send_ep3_confirm_tournament_entry(c, this->shared_from_this()); } @@ -716,8 +716,8 @@ void Tournament::send_all_state_updates_on_deletion() const { for (const auto& player : team->players) { auto c = player.client.lock(); if (c && - (c->flags & Client::Flag::IS_EPISODE_3) && - !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) && + c->config.check_flag(Client::Flag::IS_EPISODE_3) && + !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) && (c->ep3_tournament_team.lock() == team)) { send_ep3_confirm_tournament_entry(c, nullptr); } @@ -916,7 +916,7 @@ shared_ptr TournamentIndex::team_for_serial_number(uint32_t se } void TournamentIndex::link_client(shared_ptr c) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { return; } @@ -927,7 +927,7 @@ void TournamentIndex::link_client(shared_ptr c) { if (player.serial_number == c->license->serial_number) { c->ep3_tournament_team = team; player.client = c; - if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { send_ep3_confirm_tournament_entry(c, tourn); } return; @@ -936,7 +936,7 @@ void TournamentIndex::link_client(shared_ptr c) { throw logic_error("tournament team found for player, but player not found on team"); } else { c->ep3_tournament_team.reset(); - if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (!c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { send_ep3_confirm_tournament_entry(c, nullptr); } } diff --git a/src/Lobby.cc b/src/Lobby.cc index b80fc924..843270ae 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -29,7 +29,7 @@ Lobby::Lobby(shared_ptr s, uint32_t id) block(0), leader_id(0), max_clients(12), - flags(0) { + enabled_flags(0) { for (size_t x = 0; x < 12; x++) { this->next_item_id[x] = 0x00010000 + 0x00200000 * x; } @@ -83,7 +83,7 @@ void Lobby::create_ep3_server() { this->log.info("Recreating Episode 3 server state"); } auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr; - bool is_trial = (this->flags & Lobby::Flag::IS_EP3_TRIAL); + bool is_trial = this->check_flag(Lobby::Flag::IS_EP3_TRIAL); Episode3::Server::Options options = { .card_index = is_trial ? s->ep3_card_index_trial : s->ep3_card_index, .map_index = s->ep3_map_index, @@ -111,10 +111,13 @@ void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) { bool Lobby::any_client_loading() const { for (size_t x = 0; x < this->max_clients; x++) { - if (!this->clients[x].get()) { + auto lc = this->clients[x]; + if (!lc.get()) { continue; } - if (this->clients[x]->flags & (Client::Flag::LOADING | Client::Flag::LOADING_QUEST | Client::Flag::LOADING_RUNNING_QUEST)) { + if (lc->config.check_flag(Client::Flag::LOADING) || + lc->config.check_flag(Client::Flag::LOADING_QUEST) || + lc->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) { return true; } } @@ -133,7 +136,7 @@ size_t Lobby::count_clients() const { void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { ssize_t index; - ssize_t min_client_id = (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0; + ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0; if (required_client_id >= 0) { if (this->clients[required_client_id].get()) { @@ -142,7 +145,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { this->clients[required_client_id] = c; index = required_client_id; - } else if (c->options.debug) { + } else if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { for (index = max_clients - 1; index >= min_client_id; index--) { if (!this->clients[index].get()) { this->clients[index] = c; @@ -180,7 +183,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { // If the lobby is a game and item tracking is enabled, assign the inventory's // item IDs - if (this->is_game() && (this->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (this->is_game() && this->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = this->require_server_state(); auto p = c->game_data.player(); auto& inv = p->inventory; @@ -196,7 +199,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { auto p = c->game_data.player(); PlayerLobbyDataDCGC lobby_data; lobby_data.player_tag = 0x00010000; - lobby_data.guild_card = c->license->serial_number; + lobby_data.guild_card_number = c->license->serial_number; lobby_data.name.encode(p->disp.name.decode(c->language()), c->language()); this->battle_record->add_player( lobby_data, @@ -207,7 +210,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { // Send spectator count notifications if needed if (this->is_game() && this->is_ep3()) { - if (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { + if (this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { auto watched_l = this->watched_lobby.lock(); if (watched_l) { send_ep3_update_game_metadata(watched_l); @@ -247,7 +250,7 @@ void Lobby::remove_client(shared_ptr c) { // If the lobby is Episode 3, update the appropriate spectator counts if (this->is_game() && this->is_ep3()) { - if (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { + if (this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { auto watched_l = this->watched_lobby.lock(); if (watched_l) { send_ep3_update_game_metadata(watched_l); @@ -271,7 +274,7 @@ void Lobby::move_client_to_lobby( throw out_of_range("required slot is in use"); } } else { - ssize_t min_client_id = (this->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0; + ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0; size_t available_slots = dest_lobby->max_clients - min_client_id; if (dest_lobby->count_clients() >= available_slots) { throw out_of_range("no space left in lobby"); diff --git a/src/Lobby.hh b/src/Lobby.hh index e5cca47e..0e2a0d34 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -24,7 +24,7 @@ struct ServerState; struct Lobby : public std::enable_shared_from_this { - enum Flag { + enum class Flag { GAME = 0x00000001, PERSISTENT = 0x00000002, @@ -113,7 +113,7 @@ struct Lobby : public std::enable_shared_from_this { uint8_t block; uint8_t leader_id; uint8_t max_clients; - uint32_t flags; + uint32_t enabled_flags; std::shared_ptr quest; std::array, 12> clients; // Keys in this map are client_id @@ -125,18 +125,31 @@ struct Lobby : public std::enable_shared_from_this { Lobby& operator=(const Lobby&) = delete; Lobby& operator=(Lobby&&) = delete; + [[nodiscard]] inline bool check_flag(Flag flag) const { + return !!(this->enabled_flags & static_cast(flag)); + } + inline void set_flag(Flag flag) { + this->enabled_flags |= static_cast(flag); + } + inline void clear_flag(Flag flag) { + this->enabled_flags &= (~static_cast(flag)); + } + inline void toggle_flag(Flag flag) { + this->enabled_flags ^= static_cast(flag); + } + std::shared_ptr require_server_state() const; void create_item_creator(); void create_ep3_server(); - inline bool is_game() const { - return this->flags & Flag::GAME; + [[nodiscard]] inline bool is_game() const { + return this->check_flag(Flag::GAME); } - inline bool is_ep3() const { + [[nodiscard]] inline bool is_ep3() const { return this->episode == Episode::EP3; } - inline bool version_is_allowed(QuestScriptVersion v) const { + [[nodiscard]] inline bool version_is_allowed(QuestScriptVersion v) const { return this->allowed_versions & (1 << static_cast(v)); } inline void allow_version(QuestScriptVersion v) { diff --git a/src/Player.hh b/src/Player.hh index 069b6a23..d7ac73b3 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -86,7 +86,7 @@ struct SavedAccountDataBB { // .nsa file format parray blocked_senders; PSOBBGuildCardFile guild_card_file; PSOBBSystemFile system_file; - le_uint32_t newserv_flags; + le_uint32_t unused; le_uint32_t option_flags; parray shortcuts; parray symbol_chats; diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 4f5aaf7d..082375ec 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -237,7 +237,7 @@ void PlayerBank::save(const string& filename, bool save_to_filesystem) const { void PlayerLobbyDataPC::clear() { this->player_tag = 0; - this->guild_card = 0; + this->guild_card_number = 0; this->ip_address = 0; this->client_id = 0; this->name.clear(); @@ -245,7 +245,7 @@ void PlayerLobbyDataPC::clear() { void PlayerLobbyDataDCGC::clear() { this->player_tag = 0; - this->guild_card = 0; + this->guild_card_number = 0; this->ip_address = 0; this->client_id = 0; this->name.clear(); @@ -263,7 +263,7 @@ void XBNetworkLocation::clear() { void PlayerLobbyDataXB::clear() { this->player_tag = 0; - this->guild_card = 0; + this->guild_card_number = 0; this->netloc.clear(); this->client_id = 0; this->name.clear(); @@ -271,12 +271,13 @@ void PlayerLobbyDataXB::clear() { void PlayerLobbyDataBB::clear() { this->player_tag = 0; - this->guild_card = 0; - this->ip_address = 0; + this->guild_card_number = 0; + this->team_guild_card_number = 0; + this->team_id = 0; this->unknown_a1.clear(0); this->client_id = 0; this->name.clear(); - this->unknown_a2 = 0; + this->hide_help_prompt = 0; } PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec) diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index e4e8764b..3ad33571 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -238,7 +238,7 @@ struct GuildCardBB { struct PlayerLobbyDataPC { le_uint32_t player_tag = 0; - le_uint32_t guild_card = 0; + le_uint32_t guild_card_number = 0; // There's a strange behavior (bug? "feature"?) in Episode 3 where the start // button does nothing in the lobby (hence you can't "quit game") if the // client's IP address is zero. So, we fill it in with a fake nonzero value to @@ -253,7 +253,7 @@ struct PlayerLobbyDataPC { struct PlayerLobbyDataDCGC { le_uint32_t player_tag = 0; - le_uint32_t guild_card = 0; + le_uint32_t guild_card_number = 0; be_uint32_t ip_address = 0x7F000001; le_uint32_t client_id = 0; pstring name; @@ -275,7 +275,7 @@ struct XBNetworkLocation { struct PlayerLobbyDataXB { le_uint32_t player_tag = 0; - le_uint32_t guild_card = 0; + le_uint32_t guild_card_number = 0; XBNetworkLocation netloc; le_uint32_t client_id = 0; pstring name; @@ -284,15 +284,17 @@ struct PlayerLobbyDataXB { } __attribute__((packed)); struct PlayerLobbyDataBB { - le_uint32_t player_tag = 0; - le_uint32_t guild_card = 0; - // This field is a guess; the official builds didn't use this, but all other - // versions have it - be_uint32_t ip_address = 0x7F000001; - parray unknown_a1; - le_uint32_t client_id = 0; - pstring name; - le_uint32_t unknown_a2 = 0; + /* 00 */ le_uint32_t player_tag = 0; + /* 04 */ le_uint32_t guild_card_number = 0; + /* 08 */ le_uint32_t team_guild_card_number = 0; + /* 0C */ le_uint32_t team_id = 0; + /* 10 */ parray unknown_a1; + /* 1C */ le_uint32_t client_id = 0; + /* 20 */ pstring name; + // If this field is zero, the "Press F1 for help" prompt appears in the corner + // of the screen in the lobby and on Pioneer 2. + /* 40 */ le_uint32_t hide_help_prompt = 1; + /* 44 */ void clear(); } __attribute__((packed)); diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 0a5ba43b..4b4b31f5 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -122,13 +122,13 @@ static HandlerResult C_05(shared_ptr ses, uint16_t, } static HandlerResult C_1D(shared_ptr ses, uint16_t, uint32_t, string&) { - return ses->options.suppress_client_pings + return ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_CLIENT_PINGS) ? HandlerResult::Type::SUPPRESS : HandlerResult::Type::FORWARD; } static HandlerResult S_1D(shared_ptr ses, uint16_t, uint32_t, string&) { - if (ses->options.suppress_client_pings) { + if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_CLIENT_PINGS)) { ses->server_channel.send(0x1D); return HandlerResult::Type::SUPPRESS; } else { @@ -139,13 +139,13 @@ static HandlerResult S_1D(shared_ptr ses, uint16_t, static HandlerResult S_97(shared_ptr ses, uint16_t, uint32_t flag, string&) { // If the client has already received a 97 command, block this one and // immediately respond with a B1. - if (ses->newserv_client_config.cfg.flags & Client::Flag::SAVE_ENABLED) { + if (ses->config.check_flag(Client::Flag::SAVE_ENABLED)) { ses->server_channel.send(0xB1, 0x00); return HandlerResult::Type::SUPPRESS; } else { // Update the newserv client config so we'll know not to show the Programs // menu if they return to newserv - ses->newserv_client_config.cfg.flags |= Client::Flag::SAVE_ENABLED; + ses->config.set_flag(Client::Flag::PROXY_SUPPRESS_CLIENT_PINGS); // Trap any 97 command that would have triggered cheat protection, and // always send 97 01 04 00 if (flag == 0) { @@ -156,11 +156,14 @@ static HandlerResult S_97(shared_ptr ses, uint16_t, } static HandlerResult C_G_9E(shared_ptr ses, uint16_t, uint32_t, string&) { - if (ses->options.suppress_remote_login) { + if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) { le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); - S_UpdateClientConfig_DC_PC_V3_04 cmd = {{0x00010000, ses->license->serial_number, ClientConfig()}}; + S_UpdateClientConfig_V3_04 cmd; + cmd.player_tag = 0x00010000; + cmd.guild_card_number = ses->license->serial_number; + cmd.client_config.clear(0xFF); ses->client_channel.send(0x04, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -171,7 +174,7 @@ static HandlerResult C_G_9E(shared_ptr ses, uint16_t } static HandlerResult S_G_9A(shared_ptr ses, uint16_t, uint32_t, string&) { - if (!ses->license || ses->options.suppress_remote_login) { + if (!ses->license || ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) { return HandlerResult::Type::FORWARD; } @@ -192,12 +195,12 @@ static HandlerResult S_G_9A(shared_ptr ses, uint16_t cmd.access_key.encode(ses->license->access_key); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - if (ses->options.blank_name) { + if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { cmd.name.encode(" ", ses->language()); } else { cmd.name.encode(ses->character_name, ses->language()); } - cmd.client_config.data = ses->remote_client_config_data; + cmd.client_config = ses->remote_client_config_data; // If there's a guild card number, a shorter 9E is sent that ends // right after the client config data @@ -272,7 +275,7 @@ static HandlerResult S_V123P_02_17( return HandlerResult::Type::SUPPRESS; } else if ((ses->version() == GameVersion::DC) || (ses->version() == GameVersion::PC)) { - if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_DC_V1) { + if (ses->config.check_flag(Client::Flag::IS_DC_V1)) { if (command == 0x17) { C_LoginV1_DC_PC_V3_90 cmd; cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); @@ -342,7 +345,7 @@ static HandlerResult S_V123P_02_17( cmd.access_key.clear_after(8); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - if (ses->options.blank_name) { + if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { cmd.name.encode(" ", ses->language()); } else { cmd.name.encode(ses->character_name); @@ -364,7 +367,7 @@ static HandlerResult S_V123P_02_17( ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; - } else if (ses->options.suppress_remote_login) { + } else if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) { uint32_t guild_card_number; if (ses->remote_guild_card_number >= 0) { guild_card_number = ses->remote_guild_card_number; @@ -393,12 +396,12 @@ static HandlerResult S_V123P_02_17( cmd.access_key.encode(fake_access_key_str); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - if (ses->options.blank_name) { + if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { cmd.name.encode(" ", ses->language()); } else { cmd.name.encode(ses->character_name, ses->language()); } - cmd.client_config.data = ses->remote_client_config_data; + cmd.client_config = ses->remote_client_config_data; ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E)); return HandlerResult::Type::SUPPRESS; @@ -474,7 +477,7 @@ static HandlerResult S_B_03(shared_ptr ses, uint16_t static HandlerResult S_V123_04(shared_ptr ses, uint16_t, uint32_t, string& data) { // Suppress extremely short commands from the server instead of disconnecting. - if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_V3_04, cfg)) { + if (data.size() < offsetof(S_UpdateClientConfig_V3_04, client_config)) { le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); return HandlerResult::Type::SUPPRESS; @@ -482,9 +485,9 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 // Some servers send a short 04 command if they don't use all of the 0x20 // bytes available. We should be prepared to handle that. - auto& cmd = check_size_t(data, - offsetof(S_UpdateClientConfig_DC_PC_V3_04, cfg), - sizeof(S_UpdateClientConfig_DC_PC_V3_04)); + auto& cmd = check_size_t(data, + offsetof(S_UpdateClientConfig_V3_04, client_config), + sizeof(S_UpdateClientConfig_V3_04)); // If this is a licensed session, hide the guild card number assigned by the // remote server so the client doesn't see it change. If this is an unlicensed @@ -515,8 +518,8 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 ? "t Lobby Server. Copyright SEGA E" : "t Port Map. Copyright SEGA Enter", ses->remote_client_config_data.bytes()); - memcpy(ses->remote_client_config_data.data(), &cmd.cfg, - min(data.size() - offsetof(S_UpdateClientConfig_DC_PC_V3_04, cfg), + memcpy(ses->remote_client_config_data.data(), &cmd.client_config, + min(data.size() - offsetof(S_UpdateClientConfig_V3_04, client_config), ses->remote_client_config_data.bytes())); // If the guild card number was not set, pretend (to the server) that this is @@ -609,11 +612,11 @@ static HandlerResult S_B1(shared_ptr ses, uint16_t, static HandlerResult S_B2(shared_ptr ses, uint16_t, uint32_t flag, string& data) { const auto& cmd = check_size_t(data, 0xFFFF); - if (cmd.code_size && ses->options.save_files) { + if (cmd.code_size && ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { uint64_t filename_timestamp = now(); string code = data.substr(sizeof(S_ExecuteCode_B2)); - if (ses->newserv_client_config.cfg.flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) { + if (ses->config.check_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL)) { StringReader r(code); bool is_big_endian = (ses->version() == GameVersion::GC || ses->version() == GameVersion::DC); uint32_t decompressed_size = is_big_endian ? r.get_u32b() : r.get_u32l(); @@ -696,11 +699,11 @@ static HandlerResult S_B2(shared_ptr ses, uint16_t, #endif } - if (ses->options.function_call_return_value >= 0) { + if (ses->config.check_flag(Client::Flag::PROXY_BLOCK_FUNCTION_CALLS)) { ses->log.info("Blocking function call from server"); C_ExecuteCodeResult_B3 cmd; - cmd.return_value = ses->options.function_call_return_value; - cmd.checksum = 0; + cmd.return_value = 0xFFFFFFFF; + cmd.checksum = 0x00000000; ses->server_channel.send(0xB3, flag, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } else { @@ -727,7 +730,7 @@ static HandlerResult C_B3(shared_ptr ses, uint16_t, } static HandlerResult S_B_E7(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { string output_filename = string_printf("player.%" PRId64 ".bin", now()); save_file(output_filename, data); ses->log.info("Wrote player data to file %s", output_filename.c_str()); @@ -856,7 +859,7 @@ static HandlerResult S_V3_1A_D5(shared_ptr ses, uint // has the no-close-confirmation flag set in its newserv client config, send a // fake confirmation to the remote server immediately. if (((ses->version() == GameVersion::GC) || (ses->version() == GameVersion::XB)) && - (ses->newserv_client_config.cfg.flags & Client::Flag::NO_D6)) { + ses->config.check_flag(Client::Flag::NO_D6)) { ses->server_channel.send(0xD6); } return HandlerResult::Type::FORWARD; @@ -864,19 +867,17 @@ static HandlerResult S_V3_1A_D5(shared_ptr ses, uint static HandlerResult S_V3_BB_DA(shared_ptr ses, uint16_t, uint32_t flag, string&) { // This command is supported on all V3 versions except Ep1&2 Trial - if ((ses->version() == GameVersion::GC) && - (ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) { + if ((ses->version() == GameVersion::GC) && ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { return HandlerResult::Type::SUPPRESS; - } else if ((ses->options.override_lobby_event >= 0) && - (static_cast(flag) != ses->options.override_lobby_event)) { - return HandlerResult(HandlerResult::Type::MODIFIED, 0xDA, ses->options.override_lobby_event); + } else if ((ses->config.override_lobby_event != 0xFF) && (flag != ses->config.override_lobby_event)) { + return HandlerResult(HandlerResult::Type::MODIFIED, 0xDA, ses->config.override_lobby_event); } else { return HandlerResult::Type::FORWARD; } } static HandlerResult S_6x(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { if ((ses->version() == GameVersion::GC) && (data.size() >= 0x14)) { if (static_cast(data[0]) == 0xB6) { const auto& header = check_size_t(data, 0xFFFF); @@ -911,7 +912,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, modified = true; } - if (ses->options.ep3_infinite_time && (header.subcommand == 0xB4)) { + if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED) && (header.subcommand == 0xB4)) { if (header.subsubcommand == 0x3D) { auto& cmd = check_size_t(data); if (cmd.rules.overall_time_limit || cmd.rules.phase_time_limit) { @@ -1003,18 +1004,18 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 if (ses->version() == GameVersion::BB) { auto& pd = check_size_t(data, 0xFFFF); - if (ses->options.enable_chat_filter) { + if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { pd.info_board.encode(add_color(pd.info_board.decode(ses->language())), ses->language()); } - if (ses->options.blank_name) { + if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { pd.disp.name.encode(" ", ses->language()); modified = true; } - if (ses->options.red_name && pd.disp.visual.name_color != 0xFFFF0000) { + if (ses->config.check_flag(Client::Flag::PROXY_RED_NAME_ENABLED) && pd.disp.visual.name_color != 0xFFFF0000) { pd.disp.visual.name_color = 0xFFFF0000; pd.records.challenge.title_color = 0x7C00; modified = true; - } else if (ses->options.blank_name && pd.disp.visual.name_color != 0x00000000) { + } else if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED) && pd.disp.visual.name_color != 0x00000000) { pd.disp.visual.name_color = 0x00000000; modified = true; } @@ -1040,18 +1041,18 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 } else { pd = &check_size_t(data, 0xFFFF); } - if (ses->options.enable_chat_filter) { + if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { pd->info_board.encode(add_color(pd->info_board.decode(ses->language())), ses->language()); } - if (ses->options.blank_name) { + if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { pd->disp.visual.name.encode(" ", ses->language()); modified = true; } - if (ses->options.red_name && pd->disp.visual.name_color != 0xFFFF0000) { + if (ses->config.check_flag(Client::Flag::PROXY_RED_NAME_ENABLED) && pd->disp.visual.name_color != 0xFFFF0000) { pd->disp.visual.name_color = 0xFFFF0000; pd->records.challenge.stats.title_color = 0x7C00; modified = true; - } else if (ses->options.blank_name && pd->disp.visual.name_color != 0x00000000) { + } else if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED) && pd->disp.visual.name_color != 0x00000000) { pd->disp.visual.name_color = 0x00000000; modified = true; } @@ -1065,7 +1066,7 @@ static HandlerResult C_GXB_61(shared_ptr ses, uint16 } static HandlerResult C_GX_D9(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->options.enable_chat_filter) { + if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { data = add_color(data); // TODO: We should check if the info board text was actually modified and // return MODIFIED if so. @@ -1074,7 +1075,7 @@ static HandlerResult C_GX_D9(shared_ptr ses, uint16_ } static HandlerResult C_B_D9(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->options.enable_chat_filter) { + if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { try { string decoded = tt_utf16_to_utf8(data.data(), data.size()); add_color_inplace(decoded); @@ -1095,13 +1096,13 @@ static HandlerResult S_44_A6(shared_ptr ses, uint16_ string filename = cmd.filename.decode(); string output_filename; bool is_download = (command == 0xA6); - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { size_t extension_offset = filename.rfind('.'); string basename, extension; if (extension_offset != string::npos) { basename = filename.substr(0, extension_offset); extension = filename.substr(extension_offset); - if (extension == ".bin" && (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + if (extension == ".bin" && ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { extension += ".mnm"; } } else { @@ -1124,10 +1125,10 @@ static HandlerResult S_44_A6(shared_ptr ses, uint16_ } // Episode 3 download quests aren't DLQ-encoded - bool decode_dlq = is_download && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3); + bool decode_dlq = is_download && !ses->config.check_flag(Client::Flag::IS_EPISODE_3); ProxyServer::LinkedSession::SavingFile sf(filename, output_filename, cmd.file_size, decode_dlq); ses->saving_files.emplace(filename, std::move(sf)); - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { ses->log.info("Saving %s from server to %s", filename.c_str(), output_filename.c_str()); } else { ses->log.info("Tracking file %s", filename.c_str()); @@ -1167,14 +1168,14 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ if (!sf->output_filename.empty()) { ses->log.info("Adding %" PRIu32 " bytes to %s => %s", cmd.data_size.load(), sf->basename.c_str(), sf->output_filename.c_str()); - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { sf->blocks.emplace_back(reinterpret_cast(cmd.data.data()), cmd.data_size); } } sf->remaining_bytes -= cmd.data_size; if (sf->remaining_bytes == 0) { - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { ses->log.info("Writing file %s => %s", sf->basename.c_str(), sf->output_filename.c_str()); sf->write(); } else { @@ -1187,8 +1188,8 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ } static HandlerResult S_G_B7(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { - if (ses->options.ep3_infinite_meseta) { + if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { auto& cmd = check_size_t(data); if (cmd.current_meseta != 1000000) { cmd.current_meseta = 1000000; @@ -1203,7 +1204,7 @@ static HandlerResult S_G_B7(shared_ptr ses, uint16_t } static HandlerResult S_G_B8(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { if (data.size() < 4) { ses->log.warning("Card list data size is too small; not saving file"); return HandlerResult::Type::FORWARD; @@ -1223,16 +1224,15 @@ static HandlerResult S_G_B8(shared_ptr ses, uint16_t // Unset the flag specifying that the client has newserv's card definitions, // so the file sill be sent again if the client returns to newserv. - ses->newserv_client_config.cfg.flags &= ~Client::Flag::HAS_EP3_CARD_DEFS; + ses->config.clear_flag(Client::Flag::HAS_EP3_CARD_DEFS); - return (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) + return ses->config.check_flag(Client::Flag::IS_EPISODE_3) ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; } static HandlerResult S_G_B9(shared_ptr ses, uint16_t, uint32_t, string& data) { - - if (ses->options.save_files) { + if (ses->config.check_flag(Client::Flag::PROXY_SAVE_FILES)) { try { const auto& header = check_size_t(data, 0xFFFF); @@ -1259,14 +1259,14 @@ static HandlerResult S_G_B9(shared_ptr ses, uint16_t } } - return (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) + return ses->config.check_flag(Client::Flag::IS_EPISODE_3) ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; } static HandlerResult S_G_EF(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { - if (ses->options.ep3_infinite_meseta) { + if (ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { + if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { auto& cmd = check_size_t(data, offsetof(S_StartCardAuction_GC_Ep3_EF, unused), 0xFFFF); if (cmd.points_available != 0x7FFF) { @@ -1285,7 +1285,7 @@ static HandlerResult S_B_EF(shared_ptr, uint16_t, ui } static HandlerResult S_G_BA(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->options.ep3_infinite_meseta) { + if (ses->config.check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) { auto& cmd = check_size_t(data); if (cmd.current_meseta != 1000000) { cmd.current_meseta = 1000000; @@ -1299,7 +1299,7 @@ static void update_leader_id(shared_ptr ses, uint8_t if (ses->leader_client_id != leader_id) { ses->leader_client_id = leader_id; ses->log.info("Changed room leader to %zu", ses->leader_client_id); - if (ses->options.enable_player_notifications && (ses->leader_client_id == ses->lobby_client_id)) { + if (ses->config.check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED) && (ses->leader_client_id == ses->lobby_client_id)) { send_text_message(ses->client_channel, "$C6You are now the leader"); } } @@ -1318,8 +1318,8 @@ static HandlerResult S_65_67_68_EB(shared_ptr ses, u // behavior in the client config, so if it happens during a proxy session, // update the client config that we'll restore if the client uses the change // ship or change block command. - if (ses->newserv_client_config.cfg.flags & Client::Flag::NO_D6_AFTER_LOBBY) { - ses->newserv_client_config.cfg.flags |= Client::Flag::NO_D6; + if (ses->config.check_flag(Client::Flag::NO_D6_AFTER_LOBBY)) { + ses->config.set_flag(Client::Flag::NO_D6); } } @@ -1338,16 +1338,17 @@ static HandlerResult S_65_67_68_EB(shared_ptr ses, u } else { string name = entry.disp.visual.name.decode(entry.inventory.language); - if (ses->license && (entry.lobby_data.guild_card == ses->remote_guild_card_number)) { - entry.lobby_data.guild_card = ses->license->serial_number; + if (ses->license && (entry.lobby_data.guild_card_number == ses->remote_guild_card_number)) { + entry.lobby_data.guild_card_number = ses->license->serial_number; num_replacements++; modified = true; - } else if (ses->options.enable_player_notifications && command != 0x67) { + } else if (ses->config.check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED) && + (command != 0x67)) { send_text_message_printf(ses->client_channel, "$C6Join: %zu/%" PRIu32 "\n%s", - index, entry.lobby_data.guild_card.load(), name.c_str()); + index, entry.lobby_data.guild_card_number.load(), name.c_str()); } auto& p = ses->lobby_players[index]; - p.guild_card_number = entry.lobby_data.guild_card; + p.guild_card_number = entry.lobby_data.guild_card_number; p.name = name; p.language = entry.inventory.language; p.section_id = entry.disp.visual.section_id; @@ -1360,12 +1361,12 @@ static HandlerResult S_65_67_68_EB(shared_ptr ses, u ses->log.warning("Proxied player appears multiple times in lobby"); } - if (ses->options.override_lobby_event >= 0) { - cmd.lobby_flags.event = ses->options.override_lobby_event; + if (ses->config.override_lobby_event != 0xFF) { + cmd.lobby_flags.event = ses->config.override_lobby_event; modified = true; } - if (ses->options.override_lobby_number >= 0) { - cmd.lobby_flags.lobby_number = ses->options.override_lobby_number; + if (ses->config.override_lobby_number != 0x80) { + cmd.lobby_flags.lobby_number = ses->config.override_lobby_number; modified = true; } @@ -1398,12 +1399,12 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, ses->lobby_client_id = cmd->client_id; update_leader_id(ses, cmd->leader_id); for (size_t x = 0; x < flag; x++) { - if (cmd->lobby_data[x].guild_card == ses->remote_guild_card_number) { - cmd->lobby_data[x].guild_card = ses->license->serial_number; + if (cmd->lobby_data[x].guild_card_number == ses->remote_guild_card_number) { + cmd->lobby_data[x].guild_card_number = ses->license->serial_number; modified = true; } auto& p = ses->lobby_players[x]; - p.guild_card_number = cmd->lobby_data[x].guild_card; + p.guild_card_number = cmd->lobby_data[x].guild_card_number; if (cmd_ep3) { const auto& p_ep3 = cmd_ep3->players_ep3[x]; p.language = p_ep3.inventory.language; @@ -1417,16 +1418,16 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, x, p.guild_card_number, p.name.c_str()); } - if (ses->options.override_section_id >= 0) { - cmd->section_id = ses->options.override_section_id; + if (ses->config.override_section_id != 0xFF) { + cmd->section_id = ses->config.override_section_id; modified = true; } - if (ses->options.override_lobby_event >= 0) { - cmd->event = ses->options.override_lobby_event; + if (ses->config.override_lobby_event != 0xFF) { + cmd->event = ses->config.override_lobby_event; modified = true; } - if (ses->options.override_random_seed >= 0) { - cmd->rare_seed = ses->options.override_random_seed; + if (ses->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) { + cmd->rare_seed = ses->config.override_random_seed; modified = true; } @@ -1456,8 +1457,8 @@ static HandlerResult S_E8(shared_ptr ses, uint16_t, auto& player_entry = (x < 4) ? cmd.players[x] : cmd.spectator_players[x - 4]; auto& spec_entry = cmd.entries[x]; - if (player_entry.lobby_data.guild_card == ses->remote_guild_card_number) { - player_entry.lobby_data.guild_card = ses->license->serial_number; + if (player_entry.lobby_data.guild_card_number == ses->remote_guild_card_number) { + player_entry.lobby_data.guild_card_number = ses->license->serial_number; modified = true; } if (spec_entry.guild_card_number == ses->remote_guild_card_number) { @@ -1466,25 +1467,24 @@ static HandlerResult S_E8(shared_ptr ses, uint16_t, } auto& p = ses->lobby_players[x]; - p.guild_card_number = player_entry.lobby_data.guild_card; + p.guild_card_number = player_entry.lobby_data.guild_card_number; p.language = player_entry.inventory.language; p.name = player_entry.disp.visual.name.decode(p.language); p.section_id = player_entry.disp.visual.section_id; p.char_class = player_entry.disp.visual.char_class; - ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", - x, p.guild_card_number, p.name.c_str()); + ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", x, p.guild_card_number, p.name.c_str()); } - if (ses->options.override_section_id >= 0) { - cmd.section_id = ses->options.override_section_id; + if (ses->config.override_section_id != 0xFF) { + cmd.section_id = ses->config.override_section_id; modified = true; } - if (ses->options.override_lobby_event >= 0) { - cmd.event = ses->options.override_lobby_event; + if (ses->config.override_lobby_event != 0xFF) { + cmd.event = ses->config.override_lobby_event; modified = true; } - if (ses->options.override_random_seed >= 0) { - cmd.rare_seed = ses->options.override_random_seed; + if (ses->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) { + cmd.rare_seed = ses->config.override_random_seed; modified = true; } @@ -1507,7 +1507,7 @@ static HandlerResult S_66_69_E9(shared_ptr ses, uint ses->log.warning("Lobby leave command references missing position"); } else { auto& p = ses->lobby_players[index]; - if (ses->options.enable_player_notifications) { + if (ses->config.check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED)) { send_text_message_printf(ses->client_channel, "$C4Leave: %zu/%" PRIu32 "\n%s", index, p.guild_card_number, p.name.c_str()); } @@ -1545,9 +1545,7 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, text.push_back(0); } text = tt_decode_marked(text, ses->language(), true); - } else if (!text.empty() && - (text[0] != '\t') && - (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + } else if (!text.empty() && (text[0] != '\t') && ses->config.check_flag(Client::Flag::IS_EPISODE_3)) { private_flags = text[0]; text = tt_decode_marked(text.substr(1), ses->language(), false); } else { @@ -1560,12 +1558,12 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, bool is_command = (text[0] == '$') || (text[0] == '\t' && text[1] != 'C' && text[2] == '$'); - if (is_command && ses->options.enable_chat_commands) { + if (is_command && ses->config.check_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED)) { size_t offset = ((text[0] & 0xF0) == 0x40) ? 1 : 0; offset += (text[offset] == '$') ? 0 : 2; text = text.substr(offset); if (text.size() >= 2 && text[1] == '$') { - if (ses->options.enable_chat_filter) { + if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { send_chat_message_from_client(ses->server_channel, add_color(text.substr(1)), private_flags); } else { send_chat_message_from_client(ses->server_channel, text.substr(1), private_flags); @@ -1576,7 +1574,7 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, return HandlerResult::Type::SUPPRESS; } - } else if (ses->options.enable_chat_filter) { + } else if (ses->config.check_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED)) { send_chat_message_from_client(ses->server_channel, add_color(text), private_flags); return HandlerResult::Type::SUPPRESS; @@ -1651,7 +1649,7 @@ static HandlerResult C_6x(shared_ptr ses, uint16_t c ses->area = cmd.area; } else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) { - if (ses->options.infinite_hp) { + if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { send_player_stats_change(ses->client_channel, ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550); send_player_stats_change(ses->server_channel, @@ -1666,7 +1664,7 @@ static HandlerResult C_6x(shared_ptr ses, uint16_t c } else if (data[0] == 0x42) { C_6x_movement(ses, data); } else if (data[0] == 0x48) { - if (ses->options.infinite_tp) { + if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) { send_player_stats_change(ses->client_channel, ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255); send_player_stats_change(ses->server_channel, @@ -1686,7 +1684,7 @@ template <> HandlerResult C_6x(shared_ptr ses, uint16_t, uint32_t, string& data) { check_implemented_subcommand(ses, data); - if (!data.empty() && (data[0] == 0x05) && ses->options.switch_assist) { + if (!data.empty() && (data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) { auto& cmd = check_size_t(data); if (cmd.flags && cmd.header.object_id != 0xFFFF) { if (ses->last_switch_enabled_command.header.subcommand == 0x05) { diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 2b0baa88..d9369814 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -262,7 +262,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 shared_ptr license; uint32_t sub_version = 0; string character_name; - ClientConfigBB client_config; + Client::Config config; string login_command_bb; string hardware_id; @@ -277,7 +277,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->channel.language = cmd.language; character_name = cmd.name.decode(ses->channel.language); hardware_id = cmd.hardware_id.decode(); - client_config.cfg.flags |= Client::Flag::IS_DC_V1; + config.set_flag(Client::Flag::IS_DC_V1); } else if (command == 0x9D) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); @@ -312,7 +312,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 sub_version = cmd.sub_version; ses->channel.language = cmd.language; character_name = cmd.name.decode(ses->channel.language); - client_config.cfg = cmd.client_config.cfg; + config.parse_from(cmd.client_config); } else if (ses->version == GameVersion::XB) { throw runtime_error("xbox licenses are not implemented"); @@ -373,13 +373,11 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 // If there's no open session for this license, then there must be a valid // destination somewhere - either in the client config or in the unlinked // session - if (client_config.cfg.magic == CLIENT_CONFIG_MAGIC) { - linked_ses.reset(new LinkedSession( - server, ses->local_port, ses->version, license, client_config)); + if (config.proxy_destination_address != 0) { + linked_ses.reset(new LinkedSession(server, ses->local_port, ses->version, license, config)); linked_ses->log.info("Opened licensed session for unlinked session based on client config"); } else if (ses->next_destination.ss_family == AF_INET) { - linked_ses.reset(new LinkedSession( - server, ses->local_port, ses->version, license, ses->next_destination)); + linked_ses.reset(new LinkedSession(server, ses->local_port, ses->version, license, ses->next_destination)); linked_ses->log.info("Opened licensed session for unlinked session based on unlinked default destination"); } else { ses->log.error("Cannot open linked session: no valid destination in client config or unlinked session"); @@ -484,15 +482,15 @@ ProxyServer::LinkedSession::LinkedSession( uint16_t local_port, GameVersion version, shared_ptr license, - const ClientConfigBB& newserv_client_config) + const Client::Config& config) : LinkedSession(server, license->serial_number, local_port, version) { this->license = license; - this->newserv_client_config = newserv_client_config; + this->config = config; memset(&this->next_destination, 0, sizeof(this->next_destination)); struct sockaddr_in* dest_sin = reinterpret_cast(&this->next_destination); dest_sin->sin_family = AF_INET; - dest_sin->sin_port = htons(this->newserv_client_config.cfg.proxy_destination_port); - dest_sin->sin_addr.s_addr = htonl(this->newserv_client_config.cfg.proxy_destination_address); + dest_sin->sin_port = htons(this->config.proxy_destination_port); + dest_sin->sin_addr.s_addr = htonl(this->config.proxy_destination_address); } ProxyServer::LinkedSession::LinkedSession( @@ -640,11 +638,11 @@ void ProxyServer::LinkedSession::on_error(Channel& ch, short events) { if (events & BEV_EVENT_CONNECTED) { ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client"); - if (is_server_stream && (ses->options.override_lobby_event >= 0) && - (((ses->version() == GameVersion::GC) && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || + if (is_server_stream && (ses->config.override_lobby_event != 0xFF) && + (((ses->version() == GameVersion::GC) && !(ses->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) || (ses->version() == GameVersion::XB) || (ses->version() == GameVersion::BB))) { - ses->client_channel.send(0xDA, ses->options.override_lobby_event); + ses->client_channel.send(0xDA, ses->config.override_lobby_event); } } if (events & BEV_EVENT_ERROR) { @@ -705,11 +703,13 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) send_ship_info(this->client_channel, string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", s->name.c_str(), error_message ? error_message : "")); // Restore newserv_client_config, so the login server gets the client flags - S_UpdateClientConfig_DC_PC_V3_04 update_client_config_cmd; - update_client_config_cmd.player_tag = 0x00010000; - update_client_config_cmd.guild_card_number = this->license->serial_number; - update_client_config_cmd.cfg = this->newserv_client_config.cfg; - this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd)); + if (this->version() == GameVersion::GC || this->version() == GameVersion::XB) { + S_UpdateClientConfig_V3_04 update_client_config_cmd; + update_client_config_cmd.player_tag = 0x00010000; + update_client_config_cmd.guild_card_number = this->license->serial_number; + this->config.serialize_into(update_client_config_cmd.client_config); + this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd)); + } const auto& port_name = version_to_login_port_name.at(static_cast(this->version())); @@ -817,9 +817,9 @@ shared_ptr ProxyServer::get_session_by_name( shared_ptr ProxyServer::create_licensed_session( shared_ptr l, uint16_t local_port, GameVersion version, - const ClientConfigBB& newserv_client_config) { + const Client::Config& config) { shared_ptr session(new LinkedSession( - this->shared_from_this(), local_port, version, l, newserv_client_config)); + this->shared_from_this(), local_port, version, l, config)); auto emplace_ret = this->id_to_session.emplace(session->id, session); if (!emplace_ret.second) { throw runtime_error("session already exists for this license"); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index e73681d4..98db97d9 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -62,12 +62,11 @@ public: std::string hardware_id; // Only used for DC sessions std::string login_command_bb; - ClientOptions options; uint32_t challenge_rank_color_override; std::string challenge_rank_title_override; int64_t remote_guild_card_number; parray remote_client_config_data; - ClientConfigBB newserv_client_config; + Client::Config config; // A null handler in here means to forward the response to the remote server std::deque> function_call_return_handler_queue; G_SwitchStateChanged_6x05 last_switch_enabled_command; @@ -120,7 +119,7 @@ public: uint16_t local_port, GameVersion version, std::shared_ptr license, - const ClientConfigBB& newserv_client_config); + const Client::Config& config); LinkedSession( std::shared_ptr server, uint16_t local_port, @@ -179,7 +178,7 @@ public: std::shared_ptr l, uint16_t local_port, GameVersion version, - const ClientConfigBB& newserv_client_config); + const Client::Config& config); void delete_session(uint64_t id); void delete_session(struct bufferevent* bev); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 5c0ef7ea..c890fdf1 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -36,52 +36,55 @@ static shared_ptr proxy_options_menu_for_client(shared_ptr ret(new Menu(MenuID::PROXY_OPTIONS, "Proxy options")); ret->items.emplace_back(ProxyOptionsMenuItemID::GO_BACK, "Go back", "Return to the\nProxy Server menu", 0); - auto add_option = [&](uint32_t item_id, bool is_enabled, const char* text, const char* description) -> void { + auto add_bool_option = [&](uint32_t item_id, bool is_enabled, const char* text, const char* description) -> void { string option = is_enabled ? "* " : "- "; option += text; ret->items.emplace_back(item_id, option, description, 0); }; + auto add_option = [&](uint32_t item_id, Client::Flag flag, const char* text, const char* description) -> void { + add_bool_option(item_id, c->config.check_flag(flag), text, description); + }; - add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands, + add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, Client::Flag::PROXY_CHAT_COMMANDS_ENABLED, "Chat commands", "Enable chat\ncommands"); - add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter, + add_option(ProxyOptionsMenuItemID::CHAT_FILTER, Client::Flag::PROXY_CHAT_FILTER_ENABLED, "Chat filter", "Enable escape\nsequences in\nchat messages\nand info board"); - add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, c->options.enable_player_notifications, + add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED, "Player notifs", "Show a message\nwhen other players\njoin or leave"); - add_option(ProxyOptionsMenuItemID::BLOCK_PINGS, c->options.suppress_client_pings, + add_option(ProxyOptionsMenuItemID::BLOCK_PINGS, Client::Flag::PROXY_SUPPRESS_CLIENT_PINGS, "Block pings", "Block ping commands\nsent by the client"); if (s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { - add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + add_option(ProxyOptionsMenuItemID::INFINITE_HP, Client::Flag::INFINITE_HP_ENABLED, "Infinite HP", "Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); - add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, + add_option(ProxyOptionsMenuItemID::INFINITE_TP, Client::Flag::INFINITE_TP_ENABLED, "Infinite TP", "Enable automatic TP\nrestoration when\nyou cast any\ntechnique"); - add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, + add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, Client::Flag::SWITCH_ASSIST_ENABLED, "Switch assist", "Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); } else { // Note: This option's text is the maximum possible length for any menu item - add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, + add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED, "Infinite Meseta", "Fix Meseta value\nat 1,000,000"); - add_option(ProxyOptionsMenuItemID::EP3_INFINITE_TIME, c->options.ep3_infinite_time, + add_option(ProxyOptionsMenuItemID::EP3_INFINITE_TIME, Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED, "Infinite time", "Disable overall and\nper-phase time limits\nin battle"); } } - add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0), + add_bool_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->config.override_lobby_event != 0xFF), "Block events", "Disable seasonal\nevents in the lobby\nand in games"); - add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, (c->options.function_call_return_value >= 0), + add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, Client::Flag::PROXY_BLOCK_FUNCTION_CALLS, "Block patches", "Disable patches sent\nby the remote server"); if (s->proxy_allow_save_files) { - add_option(ProxyOptionsMenuItemID::SAVE_FILES, c->options.save_files, + add_option(ProxyOptionsMenuItemID::SAVE_FILES, Client::Flag::PROXY_SAVE_FILES, "Save files", "Save local copies of\nfiles from the\nremote server\n(quests, etc.)"); } if (s->proxy_enable_login_options) { - add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name, + add_option(ProxyOptionsMenuItemID::RED_NAME, Client::Flag::PROXY_RED_NAME_ENABLED, "Red name", "Set the colors\nof your name and\nChallenge Mode\nrank to red"); - add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name, + add_option(ProxyOptionsMenuItemID::BLANK_NAME, Client::Flag::PROXY_BLANK_NAME_ENABLED, "Blank name", "Suppress your\ncharacter name\nduring login"); - add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login, + add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN, "Skip login", "Use an alternate\nlogin sequence"); - add_option(ProxyOptionsMenuItemID::SKIP_CARD, c->options.zero_remote_guild_card, + add_option(ProxyOptionsMenuItemID::SKIP_CARD, Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD, "Skip card", "Use an alternate\nvalue for your initial\nGuild Card"); } @@ -102,13 +105,16 @@ static void send_client_to_proxy_server(shared_ptr c) { uint16_t local_port = s->name_to_port_config.at(port_name)->port; s->proxy_server->delete_session(c->license->serial_number); - auto session = s->proxy_server->create_licensed_session( - c->license, local_port, c->version(), c->export_config_bb()); - session->options = c->options; - session->options.save_files &= s->proxy_allow_save_files; - session->options.suppress_remote_login &= s->proxy_enable_login_options; - if (session->options.zero_remote_guild_card && s->proxy_enable_login_options) { - session->remote_guild_card_number = 0; + auto ses = s->proxy_server->create_licensed_session(c->license, local_port, c->version(), c->config); + if (!s->proxy_allow_save_files) { + ses->config.clear_flag(Client::Flag::PROXY_SAVE_FILES); + } + if (!s->proxy_enable_login_options) { + ses->config.clear_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN); + ses->config.clear_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD); + } + if (ses->config.check_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD)) { + ses->remote_guild_card_number = 0; } send_reconnect(c, s->connect_address_for_client(c), local_port); @@ -126,14 +132,13 @@ static void send_redirect_destinations_menu(shared_ptr c) { static bool send_enable_send_function_call_if_applicable(shared_ptr c) { auto s = c->require_server_state(); - if (function_compiler_available() && - (c->flags & Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL)) { + if (function_compiler_available() && c->config.check_flag(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL)) { if (s->ep3_send_function_call_enabled) { send_quest_buffer_overflow(c); } else { - c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL; + c->config.set_flag(Client::Flag::NO_SEND_FUNCTION_CALL); } - c->flags &= ~Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL; + c->config.clear_flag(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL); return true; } return false; @@ -157,7 +162,7 @@ void on_connect(std::shared_ptr c) { break; case ServerBehavior::PATCH_SERVER_BB: - c->flags |= Client::Flag::IS_BB_PATCH; + c->config.set_flag(Client::Flag::IS_BB_PATCH); send_server_init(c, 0); break; @@ -193,7 +198,7 @@ static void send_main_menu(shared_ptr c) { if (l->is_game()) { num_games++; if (l->version_is_allowed(c->quest_version()) && - (!l->is_ep3() == !(c->flags & Client::Flag::IS_EPISODE_3))) { + (l->is_ep3() == c->config.check_flag(Client::Flag::IS_EPISODE_3))) { num_compatible_games++; } } @@ -242,7 +247,7 @@ static void send_main_menu(shared_ptr c) { main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, "Download quests", "Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); if (!s->is_replay) { - if (!s->function_code_index->patch_menu_empty(c->specific_version)) { + if (!s->function_code_index->patch_menu_empty(c->config.specific_version)) { main_menu->items.emplace_back(MainMenuItemID::PATCHES, "Patches", "Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); } @@ -269,7 +274,7 @@ void on_login_complete(shared_ptr c) { // On the login server, send the events/songs, ep3 updates, and the main // menu or welcome message - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { if (s->ep3_menu_song >= 0) { send_ep3_change_music(c->channel, s->ep3_menu_song); } else if (s->pre_lobby_event) { @@ -284,9 +289,9 @@ void on_login_complete(shared_ptr c) { } if (s->welcome_message.empty() || - (c->flags & Client::Flag::NO_D6) || - !(c->flags & Client::Flag::AT_WELCOME_MESSAGE)) { - c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE; + c->config.check_flag(Client::Flag::NO_D6) || + !c->config.check_flag(Client::Flag::AT_WELCOME_MESSAGE)) { + c->config.clear_flag(Client::Flag::AT_WELCOME_MESSAGE); if (send_enable_send_function_call_if_applicable(c)) { send_update_client_config(c); } @@ -303,7 +308,7 @@ void on_login_complete(shared_ptr c) { c->game_data.should_update_play_time = true; } - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_ep3_rank_update(c); } @@ -334,13 +339,13 @@ static void set_console_client_flags(shared_ptr c, uint32_t sub_version) c->channel.version = GameVersion::DC; c->log.info("Game version changed to DC"); } else if (c->version() == GameVersion::GC) { - c->flags |= Client::Flag::IS_GC_TRIAL_EDITION; + c->config.set_flag(Client::Flag::IS_GC_TRIAL_EDITION); c->log.info("GC Trial Edition flag set"); } } - c->flags |= flags_for_version(c->version(), sub_version); - if (c->specific_version == default_specific_version_for_version(c->version(), -1)) { - c->specific_version = default_specific_version_for_version(c->version(), sub_version); + c->config.set_flags_for_version(c->version(), sub_version); + if (c->config.specific_version == default_specific_version_for_version(c->version(), -1)) { + c->config.specific_version = default_specific_version_for_version(c->version(), sub_version); } } @@ -400,8 +405,9 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) auto s = c->require_server_state(); c->channel.version = GameVersion::DC; - c->flags |= flags_for_version(c->version(), -1); - c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_DC_TRIAL_EDITION; + c->config.set_flag(Client::Flag::IS_DC_V1); + c->config.set_flag(Client::Flag::IS_DC_TRIAL_EDITION); + c->config.set_flags_for_version(c->version(), -1); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { @@ -444,8 +450,9 @@ static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) c->channel.version = GameVersion::DC; c->channel.language = cmd.language; - c->flags |= flags_for_version(c->version(), -1); - c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_DC_TRIAL_EDITION; + c->config.set_flag(Client::Flag::IS_DC_V1); + c->config.set_flag(Client::Flag::IS_DC_TRIAL_EDITION); + c->config.set_flags_for_version(c->version(), -1); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { @@ -497,8 +504,8 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, string& data) { auto s = c->require_server_state(); c->channel.version = GameVersion::DC; - c->flags |= flags_for_version(c->version(), -1); - c->flags |= Client::Flag::IS_DC_V1; + c->config.set_flag(Client::Flag::IS_DC_V1); + c->config.set_flags_for_version(c->version(), -1); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { @@ -541,7 +548,8 @@ static void on_92_DC(shared_ptr c, uint16_t, uint32_t, string& data) { // It appears that in response to 90 01, the DCv1 prototype sends 93 rather // than 92, so we use the presence of a 92 command to determine that the // client is actually DCv1 and not the prototype. - c->flags = (c->flags & ~Client::Flag::IS_DC_V1_PROTOTYPE) | Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE; + c->config.set_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE); + c->config.clear_flag(Client::Flag::IS_DC_V1_PROTOTYPE); send_command(c, 0x92, 0x01); } @@ -601,9 +609,10 @@ static void on_93_DC(shared_ptr c, uint16_t, uint32_t, string& data) { // respond with 90 01 here - that's the only case where actual DCv1 sends a // 92 command. The IS_DC_V1_PROTOTYPE flag will be removed if the client does // indeed send a 92. - if (!(c->flags & Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE)) { + if (!c->config.check_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE)) { send_command(c, 0x90, 0x01); - c->flags |= (Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE | Client::Flag::IS_DC_V1_PROTOTYPE); + c->config.set_flag(Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE); + c->config.set_flag(Client::Flag::IS_DC_V1_PROTOTYPE); } else { on_login_complete(c); } @@ -783,13 +792,11 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d } try { - c->import_config(cmd.client_config.cfg); + c->config.parse_from(cmd.client_config); } catch (const invalid_argument&) { // If we can't import the config, assume that the client was not connected // to newserv before, so we should show the welcome message. - c->flags |= Client::Flag::AT_WELCOME_MESSAGE; - c->bb_game_state = ClientStateBB::INITIAL_LOGIN; - c->game_data.bb_player_index = 0; + c->config.set_flag(Client::Flag::AT_WELCOME_MESSAGE); } } else { @@ -804,11 +811,11 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d // not; sending it again when the client has already run it will likely cause // the client to crash. if (base_cmd->unused1 == 0x5F5CA297) { - c->flags &= ~(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL | Client::Flag::NO_SEND_FUNCTION_CALL); - } else if (!s->ep3_send_function_call_enabled && - (c->flags & Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL)) { - c->flags &= ~Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL; - c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL; + c->config.clear_flag(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL); + c->config.clear_flag(Client::Flag::NO_SEND_FUNCTION_CALL); + } else if (!s->ep3_send_function_call_enabled && c->config.check_flag(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL)) { + c->config.clear_flag(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL); + c->config.set_flag(Client::Flag::NO_SEND_FUNCTION_CALL); } uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); @@ -888,7 +895,10 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { throw runtime_error("invalid size for 93 command"); } - c->flags |= flags_for_version(c->version(), -1); + c->config.set_flags_for_version(c->version(), -1); + // Note: We ignore cmd.language in this case because there are many patched + // clients out there that use the Japanese codebase but English data files + // (and hence are inaccurately reported as Japanese here). try { auto l = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); @@ -926,57 +936,41 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { try { if (is_old_format) { - c->import_config(cmd.var.old_clients_cfg.cfg); + c->config.parse_from(cmd.var.old_client_config); } else { - c->import_config(cmd.var.new_clients.cfg.cfg); - } - if (c->bb_game_state < ClientStateBB::IN_GAME) { - c->bb_game_state++; + c->config.parse_from(cmd.var.new_clients.client_config); } } catch (const invalid_argument&) { - c->bb_game_state = ClientStateBB::INITIAL_LOGIN; - c->game_data.bb_player_index = 0; } + c->bb_connection_phase = cmd.connection_phase; + c->game_data.bb_player_index = cmd.character_slot; + if (cmd.menu_id == MenuID::LOBBY) { c->preferred_lobby_id = cmd.preferred_lobby_id; } send_client_init_bb(c, 0); - switch (c->bb_game_state) { - case ClientStateBB::INITIAL_LOGIN: - // On first login, send the client to the data server port - send_reconnect(c, s->connect_address_for_client(c), - s->name_to_port_config.at("bb-data1")->port); - break; + if (cmd.guild_card_number == 0) { + // On first login, send the client to the data server port + send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at("bb-data1")->port); - case ClientStateBB::DOWNLOAD_DATA: - case ClientStateBB::CHOOSE_PLAYER: - case ClientStateBB::SAVE_PLAYER: - // Just wait in these cases; the client will request something from us and - // the command handlers will take care of it - break; - - case ClientStateBB::SHIP_SELECT: - on_login_complete(c); - break; - - case ClientStateBB::IN_GAME: - break; - - default: - throw runtime_error("invalid bb game state"); + } else if (c->bb_connection_phase >= 0x04) { + // This means the client is done with the data server phase and is in the + // game server phase; we should send the ship select menu or a lobby join + // command. + on_login_complete(c); } } static void on_9F_V3(shared_ptr c, uint16_t, uint32_t, string& data) { if (c->version() == GameVersion::BB) { - const auto& cfg = check_size_t(data); - c->import_config(cfg); + const auto& cmd = check_size_t(data); + c->config.parse_from(cmd.data); } else { - const auto& cfg = check_size_t(data); - c->import_config(cfg); + const auto& cmd = check_size_t(data); + c->config.parse_from(cmd.data); } } @@ -1079,9 +1073,9 @@ static bool add_next_game_client(shared_ptr l) { } s->change_client_lobby(c, l, true, target_client_id); - c->flags |= Client::Flag::LOADING; + c->config.set_flag(Client::Flag::LOADING); if (tourn) { - c->flags |= Client::Flag::LOADING_TOURNAMENT; + c->config.set_flag(Client::Flag::LOADING_TOURNAMENT); } c->disconnect_hooks.emplace(ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME, [s, l]() -> void { add_next_game_client(l); @@ -1343,12 +1337,12 @@ static void on_DC_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat } if (flag != 0) { - c->flags &= ~(Client::Flag::LOADING_TOURNAMENT); - l->flags |= Lobby::Flag::BATTLE_IN_PROGRESS; + c->config.clear_flag(Client::Flag::LOADING_TOURNAMENT); + l->set_flag(Lobby::Flag::BATTLE_IN_PROGRESS); send_command(c, 0xDC, 0x00); send_ep3_start_tournament_deck_select_if_all_clients_ready(l); } else { - l->flags &= ~Lobby::Flag::BATTLE_IN_PROGRESS; + l->clear_flag(Lobby::Flag::BATTLE_IN_PROGRESS); } } @@ -1419,7 +1413,7 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { PlayerLobbyDataDCGC lobby_data; lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language()); lobby_data.player_tag = 0x00010000; - lobby_data.guild_card = existing_c->license->serial_number; + lobby_data.guild_card_number = existing_c->license->serial_number; l->battle_record->add_player( lobby_data, existing_p->inventory, @@ -1540,12 +1534,12 @@ static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, string&) { static void on_D6_V3(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - if (c->flags & Client::Flag::IN_INFORMATION_MENU) { + if (c->config.check_flag(Client::Flag::IN_INFORMATION_MENU)) { auto s = c->require_server_state(); send_menu(c, s->information_menu_for_version(c->version())); - } else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) { + } else if (c->config.check_flag(Client::Flag::AT_WELCOME_MESSAGE)) { send_enable_send_function_call_if_applicable(c); - c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE; + c->config.clear_flag(Client::Flag::AT_WELCOME_MESSAGE); send_update_client_config(c); send_main_menu(c); } @@ -1592,7 +1586,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { if (!game->is_game()) { send_ship_info(c, "$C4Incorrect game ID"); - } else if ((c->flags & Client::Flag::IS_EPISODE_3) && game->is_ep3()) { + } else if (c->config.check_flag(Client::Flag::IS_EPISODE_3) && game->is_ep3()) { send_ep3_game_details(c, game); } else { @@ -1621,7 +1615,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { abbreviation_for_mode(game->mode), secid_str.c_str()); - bool cheats_enabled = game->flags & Lobby::Flag::CHEATS_ENABLED; + bool cheats_enabled = game->check_flag(Lobby::Flag::CHEATS_ENABLED); bool locked = !game->password.empty(); if (cheats_enabled && locked) { info += "$C4Locked$C7, $C6cheats enabled$C7\n"; @@ -1632,18 +1626,18 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { } if (game->quest) { - info += (game->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) ? "$C6Quest: " : "$C4Quest: "; + info += (game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) ? "$C6Quest: " : "$C4Quest: "; info += game->quest->name; info += "\n"; - } else if (game->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) { + } else if (game->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { info += "$C6Quest in progress\n"; - } else if (game->flags & Lobby::Flag::QUEST_IN_PROGRESS) { + } else if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { info += "$C4Quest in progress\n"; - } else if (game->flags & Lobby::Flag::BATTLE_IN_PROGRESS) { + } else if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) { info += "$C4Battle in progress\n"; } - if (game->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) { + if (game->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) { info += "$C4View Battle forbidden\n"; } @@ -1655,7 +1649,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS: { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_ship_info(c, "Incorrect menu ID"); break; } @@ -1667,7 +1661,7 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { } case MenuID::TOURNAMENT_ENTRIES: { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_ship_info(c, "Incorrect menu ID"); break; } @@ -1774,12 +1768,12 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { switch (item_id) { case MainMenuItemID::GO_TO_LOBBY: { c->should_send_to_lobby_server = true; - if (!(c->flags & Client::Flag::SAVE_ENABLED)) { - c->flags |= Client::Flag::SAVE_ENABLED; + if (!c->config.check_flag(Client::Flag::SAVE_ENABLED)) { + c->config.set_flag(Client::Flag::SAVE_ENABLED); // DC NTE and the v1 prototype crash if they receive a 97 command, // so we instead do the redirect immediately if ((c->version() == GameVersion::DC) && - (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { + (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE))) { send_client_to_lobby_server(c); } else { send_command(c, 0x97, 0x01); @@ -1794,7 +1788,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case MainMenuItemID::INFORMATION: { auto s = c->require_server_state(); send_menu(c, s->information_menu_for_version(c->version())); - c->flags |= Client::Flag::IN_INFORMATION_MENU; + c->config.set_flag(Client::Flag::IN_INFORMATION_MENU); break; } @@ -1808,7 +1802,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case MainMenuItemID::DOWNLOAD_QUESTS: { auto s = c->require_server_state(); - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { // Episode 3 has only download quests, not online quests, so this is // always the download quest menu. (Episode 3 does actually have // online quests, but they're served via a server data request @@ -1835,7 +1829,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { // Not Episode 3, or there are multiple Episode 3 download categories; // send the categories menu instead - uint8_t flags = (c->flags & Client::Flag::IS_EPISODE_3) + uint8_t flags = c->config.check_flag(Client::Flag::IS_EPISODE_3) ? QuestCategoryIndex::Category::Flag::EP3_DOWNLOAD : QuestCategoryIndex::Category::Flag::DOWNLOAD; if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { @@ -1849,11 +1843,11 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { if (!function_compiler_available()) { throw runtime_error("function compiler not available"); } - if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { + if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) { throw runtime_error("client does not support send_function_call"); } prepare_client_for_patches(c, [c]() -> void { - send_menu(c, c->require_server_state()->function_code_index->patch_menu(c->specific_version)); + send_menu(c, c->require_server_state()->function_code_index->patch_menu(c->config.specific_version)); }); break; @@ -1861,7 +1855,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { if (!function_compiler_available()) { throw runtime_error("function compiler not available"); } - if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { + if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) { throw runtime_error("client does not support send_function_call"); } prepare_client_for_patches(c, [c]() -> void { @@ -1887,7 +1881,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case MenuID::INFORMATION: { if (item_id == InformationMenuItemID::GO_BACK) { - c->flags &= ~Client::Flag::IN_INFORMATION_MENU; + c->config.clear_flag(Client::Flag::IN_INFORMATION_MENU); send_main_menu(c); } else { @@ -1907,60 +1901,52 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_proxy_destinations_menu(c); break; case ProxyOptionsMenuItemID::CHAT_COMMANDS: - c->options.enable_chat_commands = !c->options.enable_chat_commands; + c->config.toggle_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::CHAT_FILTER: - c->options.enable_chat_filter = !c->options.enable_chat_filter; + c->config.toggle_flag(Client::Flag::PROXY_CHAT_FILTER_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS: - c->options.enable_player_notifications = !c->options.enable_player_notifications; + c->config.toggle_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::BLOCK_PINGS: - c->options.suppress_client_pings = !c->options.suppress_client_pings; + c->config.toggle_flag(Client::Flag::PROXY_SUPPRESS_CLIENT_PINGS); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::INFINITE_HP: - c->options.infinite_hp = !c->options.infinite_hp; + c->config.toggle_flag(Client::Flag::INFINITE_HP_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::INFINITE_TP: - c->options.infinite_tp = !c->options.infinite_tp; + c->config.toggle_flag(Client::Flag::INFINITE_TP_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::SWITCH_ASSIST: - c->options.switch_assist = !c->options.switch_assist; + c->config.toggle_flag(Client::Flag::SWITCH_ASSIST_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::EP3_INFINITE_MESETA: - c->options.ep3_infinite_meseta = !c->options.ep3_infinite_meseta; + c->config.toggle_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::EP3_INFINITE_TIME: - c->options.ep3_infinite_time = !c->options.ep3_infinite_time; + c->config.toggle_flag(Client::Flag::PROXY_EP3_INFINITE_TIME_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::BLOCK_EVENTS: - if (c->options.override_lobby_event >= 0) { - c->options.override_lobby_event = -1; - } else { - c->options.override_lobby_event = 0; - } + c->config.override_lobby_event = (c->config.override_lobby_event == 0xFF) ? 0x00 : 0xFF; goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::BLOCK_PATCHES: - if (c->options.function_call_return_value >= 0) { - c->options.function_call_return_value = -1; - } else { - c->options.function_call_return_value = 0xFFFFFFFF; - } + c->config.toggle_flag(Client::Flag::PROXY_BLOCK_FUNCTION_CALLS); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::SAVE_FILES: - c->options.save_files = !c->options.save_files; + c->config.toggle_flag(Client::Flag::PROXY_SAVE_FILES); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::RED_NAME: - c->options.red_name = !c->options.red_name; + c->config.toggle_flag(Client::Flag::PROXY_RED_NAME_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::BLANK_NAME: - c->options.blank_name = !c->options.blank_name; + c->config.toggle_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::SUPPRESS_LOGIN: - c->options.suppress_remote_login = !c->options.suppress_remote_login; + c->config.toggle_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN); goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::SKIP_CARD: - c->options.zero_remote_guild_card = !c->options.zero_remote_guild_card; + c->config.toggle_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD); resend_proxy_options_menu: send_menu(c, proxy_options_menu_for_client(c)); break; @@ -1992,15 +1978,15 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { } else { // Clear Check Tactics menu so client won't see newserv tournament // state while logically on another server - if ((c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3) && !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { send_ep3_confirm_tournament_entry(c, nullptr); } - c->proxy_destination_address = resolve_ipv4(dest->first); - c->proxy_destination_port = dest->second; - if (!(c->flags & Client::Flag::SAVE_ENABLED)) { + c->config.proxy_destination_address = resolve_ipv4(dest->first); + c->config.proxy_destination_port = dest->second; + if (!c->config.check_flag(Client::Flag::SAVE_ENABLED)) { c->should_send_to_proxy_server = true; - c->flags |= Client::Flag::SAVE_ENABLED; + c->config.set_flag(Client::Flag::SAVE_ENABLED); send_command(c, 0x97, 0x01); send_update_client_config(c); } else { @@ -2033,7 +2019,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { } else { // Clear Check Tactics menu so client won't see newserv tournament // state while logically on another server - if ((c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3) && !c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { send_ep3_confirm_tournament_entry(c, nullptr); } send_reconnect(c, resolve_ipv4(dest->first), dest->second); @@ -2058,16 +2044,16 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } if (!game->version_is_allowed(c->quest_version()) || - (!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) || - (!(game->flags & Lobby::Flag::IS_EP3_TRIAL) != !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION))) { + (game->is_ep3() != c->config.check_flag(Client::Flag::IS_EPISODE_3)) || + (game->check_flag(Lobby::Flag::IS_EP3_TRIAL) != c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION))) { send_lobby_message_box(c, "$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); break; } - if (game->flags & Lobby::Flag::QUEST_IN_PROGRESS) { + if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nquest is already\nin progress."); break; } - if (game->flags & Lobby::Flag::BATTLE_IN_PROGRESS) { + if (game->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) { send_lobby_message_box(c, "$C6You cannot join this\ngame because a\nbattle is already\nin progress."); break; } @@ -2099,7 +2085,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { if (!s->change_client_lobby(c, game)) { throw logic_error("client cannot join game after all preconditions satisfied"); } - c->flags |= Client::Flag::LOADING; + c->config.set_flag(Client::Flag::LOADING); break; } @@ -2150,9 +2136,9 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } if (q->joinable) { - l->flags |= Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS; + l->set_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS); } else { - l->flags |= Lobby::Flag::QUEST_IN_PROGRESS; + l->set_flag(Lobby::Flag::QUEST_IN_PROGRESS); } l->quest = q; l->episode = q->episode; @@ -2205,8 +2191,8 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { // check/clear it later. if ((lc->version() != GameVersion::DC) && (lc->version() != GameVersion::PC) && - !(lc->flags & Client::Flag::IS_GC_TRIAL_EDITION)) { - lc->flags |= Client::Flag::LOADING_QUEST; + !lc->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { + lc->config.set_flag(Client::Flag::LOADING_QUEST); lc->disconnect_hooks.emplace(QUEST_BARRIER_DISCONNECT_HOOK_NAME, [l]() -> void { send_quest_barrier_if_all_clients_ready(l); }); @@ -2240,16 +2226,16 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_main_menu(c); } else { - if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { + if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) { throw runtime_error("client does not support send_function_call"); } auto s = c->require_server_state(); - uint64_t key = (static_cast(item_id) << 32) | c->specific_version; + uint64_t key = (static_cast(item_id) << 32) | c->config.specific_version; send_function_call( c, s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key)); c->function_call_response_queue.emplace_back(empty_function_call_response_handler); - send_menu(c, s->function_code_index->patch_menu(c->specific_version)); + send_menu(c, s->function_code_index->patch_menu(c->config.specific_version)); } break; @@ -2258,7 +2244,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { send_main_menu(c); } else { - if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { + if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) { throw runtime_error("client does not support send_function_call"); } @@ -2277,7 +2263,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case MenuID::TOURNAMENTS_FOR_SPEC: case MenuID::TOURNAMENTS: { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Episode 3 client attempted to join tournament"); } auto s = c->require_server_state(); @@ -2288,7 +2274,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } case MenuID::TOURNAMENT_ENTRIES: { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Episode 3 client attempted to join tournament"); } if (c->ep3_tournament_team.lock()) { @@ -2368,7 +2354,7 @@ static void on_84(shared_ptr c, uint16_t, uint32_t, string& data) { return; } - if (new_lobby->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) { + if (new_lobby->is_ep3() && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_lobby_message_box(c, "$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only."); return; } @@ -2404,7 +2390,7 @@ static void on_A0(shared_ptr c, uint16_t, uint32_t, string&) { // contents appear prepended to the next large message box. But, we don't have // to do this if we're not going to show the welcome message or information // menu (that is, if the client will not send a close confirmation). - if (!(c->flags & Client::Flag::NO_D6)) { + if (!c->config.check_flag(Client::Flag::NO_D6)) { send_message_box(c, ""); } @@ -2421,7 +2407,7 @@ static void on_A1(shared_ptr c, uint16_t command, uint32_t flag, string& } static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, string& data) { - if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { on_A0(c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8E"); @@ -2429,7 +2415,7 @@ static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, s } static void on_8F_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, string& data) { - if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { on_A1(c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8F"); @@ -2503,7 +2489,7 @@ static void on_A2(shared_ptr c, uint16_t, uint32_t flag, string& data) { // In Episode 3, there are no quest categories, so skip directly to the quest // filter menu. - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_lobby_message_box(c, "$C6Episode 3 does not\nprovide online quests\nvia this interface."); } else { @@ -2542,8 +2528,8 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) auto l = c->require_lobby(); - if (c->flags & Client::Flag::LOADING_RUNNING_QUEST) { - c->flags &= ~Client::Flag::LOADING_RUNNING_QUEST; + if (c->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) { + c->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST); if (l->base_version != GameVersion::BB) { throw logic_error("joinable quest started on non-BB version"); } @@ -2556,8 +2542,8 @@ static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) send_command(c, 0xAC, 0x00); send_command(leader_c, 0xDD, c->lobby_client_id); - } else if (c->flags & Client::Flag::LOADING_QUEST) { - c->flags &= ~Client::Flag::LOADING_QUEST; + } else if (c->config.check_flag(Client::Flag::LOADING_QUEST)) { + c->config.clear_flag(Client::Flag::LOADING_QUEST); if (!l->quest.get()) { return; @@ -2589,7 +2575,7 @@ static void on_AA(shared_ptr c, uint16_t, uint32_t, string& data) { if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { throw runtime_error("pre-V3 client sent update quest stats command"); } - if (c->flags & Client::Flag::IS_GC_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { throw runtime_error("trial edition client sent update quest stats command"); } @@ -2660,7 +2646,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri switch (c->version()) { case GameVersion::DC: { - if (c->flags & Client::Flag::IS_DC_V1) { + if (c->config.check_flag(Client::Flag::IS_DC_V1)) { const auto& pd = check_size_t(data); player->inventory = pd.inventory; player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language); @@ -2698,15 +2684,16 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri case GameVersion::XB: { const C_CharacterData_V3_61_98* cmd; if (flag == 4) { // Episode 3 - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Episode 3 client sent Episode 3 player data"); } const auto* pd3 = &check_size_t(data); c->game_data.ep3_config.reset(new Episode3::PlayerConfig(pd3->ep3_config)); cmd = reinterpret_cast(pd3); } else { - if (c->flags & Client::Flag::IS_EPISODE_3) { - c->flags = (c->flags | Client::Flag::IS_EP3_TRIAL_EDITION) & (~Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL); + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { + c->config.set_flag(Client::Flag::IS_EP3_TRIAL_EDITION); + c->config.clear_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL); } cmd = &check_size_t(data, 0xFFFF); } @@ -2719,17 +2706,17 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri // update and the current tournament entry depend on this flag, we have // to delay sending them until now, instead of sending them during the // login sequence. - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { bool flags_changed = false; - if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) { + if (!c->config.check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) { send_ep3_card_list_update(c); - c->flags |= Client::Flag::HAS_EP3_CARD_DEFS; + c->config.set_flag(Client::Flag::HAS_EP3_CARD_DEFS); flags_changed = true; } - if (!(c->flags & Client::Flag::HAS_EP3_MEDIA_UPDATES)) { + if (!c->config.check_flag(Client::Flag::HAS_EP3_MEDIA_UPDATES)) { for (const auto& banner : s->ep3_lobby_banners) { send_ep3_media_update(c, banner.type, banner.which, banner.data); - c->flags |= Client::Flag::HAS_EP3_MEDIA_UPDATES; + c->config.set_flag(Client::Flag::HAS_EP3_MEDIA_UPDATES); flags_changed = true; } } @@ -2853,7 +2840,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { auto l = c->lobby.lock(); char private_flags = 0; - if ((c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3) && l && l->is_ep3() && (text[0] != '\t')) { + if ((c->version() == GameVersion::GC) && c->config.check_flag(Client::Flag::IS_EPISODE_3) && l && l->is_ep3() && (text[0] != '\t')) { private_flags = text[0]; text = text.substr(1); } @@ -2897,7 +2884,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { if (l->battle_record && l->battle_record->battle_in_progress()) { auto prepared_message = prepare_chat_data( c->version(), - c->flags & Client::Flag::IS_DC_TRIAL_EDITION, + c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION), c->language(), c->lobby_client_id, p->disp.name.decode(c->language()), @@ -2910,18 +2897,14 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_00E0_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); send_system_file_bb(c); - c->game_data.account()->newserv_flags &= ~AccountFlag::IN_DRESSING_ROOM; - c->log.info("Cleared dressing room flag for account"); } static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); - if (c->bb_game_state == ClientStateBB::CHOOSE_PLAYER) { - c->game_data.bb_player_index = cmd.player_index; - c->bb_game_state++; - send_client_init_bb(c, 0); + if (c->bb_connection_phase != 0x00) { send_approve_player_choice_bb(c); + } else { if (!c->license) { c->should_disconnect = true; @@ -3101,15 +3084,8 @@ static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, st } } -static void on_00EC_BB(shared_ptr c, uint16_t, uint32_t, string& data) { - const auto& cmd = check_size_t(data); - if (cmd.reason == 2) { - c->game_data.account()->newserv_flags |= AccountFlag::IN_DRESSING_ROOM; - c->log.info("Set dressing room flag for account"); - } else { - c->game_data.account()->newserv_flags &= ~AccountFlag::IN_DRESSING_ROOM; - c->log.info("Cleared dressing room flag for account"); - } +static void on_00EC_BB(shared_ptr, uint16_t, uint32_t, string& data) { + check_size_t(data); } static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -3120,24 +3096,13 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { return; } - // Hack: We use the security data to indicate to the server which phase the - // client is in (download data, character select, lobby, etc.). This presents - // a problem: the client expects to get an E4 (approve player choice) command - // immediately after the E5 (create character) command, but the client also - // will disconnect immediately after it receives that command. If we send an - // E6 before the E5 to update the security data (setting the correct next - // state), then the client sends ANOTHER E5, but this time it's blank! So, to - // be able to both create characters correctly and set security data - // correctly, we need to process only the first E5, and ignore the second. We - // do this by only creating a player if the current connection has no loaded - // player data. if (c->game_data.player(false).get()) { - return; + throw runtime_error("player already exists"); } c->game_data.bb_player_index = cmd.player_index; - if (c->game_data.account()->newserv_flags & AccountFlag::IN_DRESSING_ROOM) { + if (c->bb_connection_phase == 0x03) { // Dressing room try { c->game_data.player()->disp.apply_dressing_room(cmd.preview); } catch (const exception& e) { @@ -3153,10 +3118,8 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { return; } } - c->game_data.account()->newserv_flags &= ~AccountFlag::IN_DRESSING_ROOM; c->log.info("Cleared dressing room flag for account"); - send_client_init_bb(c, 0); send_approve_player_choice_bb(c); } @@ -3368,7 +3331,6 @@ shared_ptr create_game_generic( Episode episode, GameMode mode, uint8_t difficulty, - uint32_t flags, bool allow_v1, shared_ptr watched_lobby, shared_ptr battle_player) { @@ -3421,32 +3383,37 @@ shared_ptr create_game_generic( return nullptr; } - bool item_tracking_enabled = (c->version() == GameVersion::BB) || s->item_tracking_enabled; - // Only disable drops if the config flag is set and are playing regular - // multi-mode. Drops are still enabled for battle and challenge modes. - bool drops_enabled = s->behavior_enabled(s->enable_drops_behavior) || (mode != GameMode::NORMAL); - bool use_server_item_table = item_tracking_enabled && s->behavior_enabled(s->use_server_item_tables_behavior); - bool cheat_mode_enabled = s->behavior_enabled(s->cheat_mode_behavior); - - bool cannot_change_cheat_mode = !s->behavior_can_be_overridden(s->cheat_mode_behavior); - bool cannot_change_item_table = !item_tracking_enabled || !s->behavior_can_be_overridden(s->use_server_item_tables_behavior); - bool cannot_change_drops_enabled = !s->behavior_can_be_overridden(s->enable_drops_behavior); - - bool is_ep3_trial = (c->version() == GameVersion::GC) && - (c->flags & Client::Flag::IS_EPISODE_3) && - (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); - shared_ptr game = s->create_lobby(); game->name = name; - game->flags = flags | - Lobby::Flag::GAME | - (is_ep3_trial ? Lobby::Flag::IS_EP3_TRIAL : 0) | - (item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) | - (drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) | - (cheat_mode_enabled ? Lobby::Flag::CHEATS_ENABLED : 0) | - (cannot_change_drops_enabled ? Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED : 0) | - (cannot_change_item_table ? Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE : 0) | - (cannot_change_cheat_mode ? Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE : 0); + game->set_flag(Lobby::Flag::GAME); + if ((c->version() == GameVersion::GC) && + c->config.check_flag(Client::Flag::IS_EPISODE_3) && + c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { + game->set_flag(Lobby::Flag::IS_EP3_TRIAL); + } + if ((c->version() == GameVersion::BB) || s->item_tracking_enabled) { + game->set_flag(Lobby::Flag::ITEM_TRACKING_ENABLED); + } + // Only disable drops if the config flag is set and for regular multi-mode; + // drops are still enabled for battle and challenge modes + if (s->behavior_enabled(s->enable_drops_behavior) || (mode != GameMode::NORMAL)) { + game->set_flag(Lobby::Flag::DROPS_ENABLED); + } + if (s->behavior_enabled(s->cheat_mode_behavior)) { + game->set_flag(Lobby::Flag::CHEATS_ENABLED); + } + if (!s->behavior_can_be_overridden(s->enable_drops_behavior)) { + game->set_flag(Lobby::Flag::CANNOT_CHANGE_DROPS_ENABLED); + } + if (!game->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED) || !s->behavior_can_be_overridden(s->use_server_item_tables_behavior)) { + game->set_flag(Lobby::Flag::CANNOT_CHANGE_ITEM_TABLE); + } + if (!s->behavior_can_be_overridden(s->cheat_mode_behavior)) { + game->set_flag(Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE); + } + if (watched_lobby || battle_player) { + game->set_flag(Lobby::Flag::IS_SPECTATOR_TEAM); + } game->password = password; game->base_version = c->version(); @@ -3497,14 +3464,14 @@ shared_ptr create_game_generic( throw logic_error("invalid quest script version"); } - game->section_id = c->options.override_section_id >= 0 - ? c->options.override_section_id + game->section_id = (c->config.override_section_id != 0xFF) + ? c->config.override_section_id : p->disp.visual.section_id; game->episode = episode; game->mode = mode; game->difficulty = difficulty; - if (c->options.override_random_seed >= 0) { - game->random_seed = c->options.override_random_seed; + if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) { + game->random_seed = c->config.override_random_seed; } game->random_crypt.reset(new PSOV2Encryption(game->random_seed)); if (battle_player) { @@ -3516,13 +3483,14 @@ shared_ptr create_game_generic( game->next_item_id[x] = (0x00200000 * x) + 0x00010000; } game->next_game_item_id = 0x00810000; - if ((game->base_version == GameVersion::BB) || use_server_item_table) { + if ((game->base_version == GameVersion::BB) || + (game->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED) && s->behavior_enabled(s->use_server_item_tables_behavior))) { game->create_item_creator(); } game->event = Lobby::game_event_for_lobby_event(current_lobby->event); game->block = 0xFF; - game->max_clients = (game->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 12 : 4; + game->max_clients = game->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 12 : 4; game->min_level = min_level; game->max_level = 0xFFFFFFFF; if (watched_lobby) { @@ -3533,7 +3501,9 @@ shared_ptr create_game_generic( bool is_solo = (game->mode == GameMode::SOLO); // Generate the map variations - if (game->is_ep3() || (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)))) { + if (game->is_ep3() || + (c->version() == GameVersion::DC && + (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE)))) { game->variations.clear(0); } else { generate_variations(game->variations, game->random_crypt, game->episode, is_solo); @@ -3599,7 +3569,7 @@ static void on_C1_PC(shared_ptr c, uint16_t, uint32_t, string& data) { auto game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, mode, cmd.difficulty); if (game) { s->change_client_lobby(c, game); - c->flags |= Client::Flag::LOADING; + c->config.set_flag(Client::Flag::LOADING); } } @@ -3607,21 +3577,21 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, str auto s = c->require_server_state(); shared_ptr game; - if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { + if ((c->version() == GameVersion::DC) && + (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE))) { const auto& cmd = check_size_t>(data); - game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, 0, true); + game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, true); } else { const auto& cmd = check_size_t(data); // Only allow E7/EC from Ep3 clients - bool client_is_ep3 = !!(c->flags & Client::Flag::IS_EPISODE_3); + bool client_is_ep3 = c->config.check_flag(Client::Flag::IS_EPISODE_3); if (((command & 0xF0) == 0xE0) != client_is_ep3) { throw runtime_error("invalid command"); } Episode episode = Episode::NONE; - uint32_t flags = 0; bool allow_v1 = false; if (c->version() == GameVersion::DC) { allow_v1 = (cmd.episode == 0); @@ -3633,12 +3603,13 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, str } GameMode mode = GameMode::NORMAL; + bool spectators_forbidden = false; if (cmd.battle_mode) { mode = GameMode::BATTLE; } if (cmd.challenge_mode) { if (client_is_ep3) { - flags |= Lobby::Flag::SPECTATORS_FORBIDDEN; + spectators_forbidden = true; } else { mode = GameMode::CHALLENGE; } @@ -3654,22 +3625,24 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, str send_lobby_message_box(c, "$C6This game no longer\nexists"); return; } - if (watched_lobby->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) { + if (watched_lobby->check_flag(Lobby::Flag::SPECTATORS_FORBIDDEN)) { send_lobby_message_box(c, "$C6This game does not\nallow spectators"); return; } - flags |= Lobby::Flag::IS_SPECTATOR_TEAM; } - game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), episode, mode, cmd.difficulty, flags, allow_v1, watched_lobby); + game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), episode, mode, cmd.difficulty, allow_v1, watched_lobby); if (game && (game->episode == Episode::EP3)) { game->ep3_ex_result_values = s->ep3_default_ex_values; + if (spectators_forbidden) { + game->set_flag(Lobby::Flag::SPECTATORS_FORBIDDEN); + } } } if (game) { s->change_client_lobby(c, game); - c->flags |= Client::Flag::LOADING; + c->config.set_flag(Client::Flag::LOADING); } } @@ -3706,12 +3679,12 @@ static void on_C1_BB(shared_ptr c, uint16_t, uint32_t, string& data) { auto game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), episode, mode, cmd.difficulty); if (game) { s->change_client_lobby(c, game); - c->flags |= Client::Flag::LOADING; + c->config.set_flag(Client::Flag::LOADING); } } static void on_8A(shared_ptr c, uint16_t, uint32_t, string& data) { - if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { + if ((c->version() == GameVersion::DC) && c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { const auto& cmd = check_size_t(data); set_console_client_flags(c, cmd.sub_version); send_command(c, 0x8A, 0x01); @@ -3730,7 +3703,7 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, string& data) { if (!l->is_game()) { throw runtime_error("client sent ready command outside of game"); } - c->flags &= (~Client::Flag::LOADING); + c->config.clear_flag(Client::Flag::LOADING); send_resume_game(l, c); if (l->base_version == GameVersion::BB) { @@ -3742,7 +3715,7 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, string& data) { // unexpectedly (that is, only equipped items are included). if (c->version() == GameVersion::BB) { send_get_player_info(c); - if (l->flags & Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS) { + if (l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { if (!l->quest) { throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest"); } @@ -3755,7 +3728,7 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, string& data) { send_open_quest_file(c, bin_basename + ".bin", bin_basename, vq->bin_contents, QuestFileType::ONLINE); send_open_quest_file(c, dat_basename + ".dat", dat_basename, vq->dat_contents, QuestFileType::ONLINE); - c->flags |= Client::Flag::LOADING_RUNNING_QUEST; + c->config.set_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST); } else if (l->map) { send_rare_enemy_index_list(c, l->map->rare_enemy_indexes); } @@ -3763,12 +3736,12 @@ static void on_6F(shared_ptr c, uint16_t, uint32_t, string& data) { // Handle initial commands for spectator teams auto watched_lobby = l->watched_lobby.lock(); - if (l->battle_player && (l->flags & Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY)) { + if (l->battle_player && l->check_flag(Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY)) { l->battle_player->start(); } else if (watched_lobby && watched_lobby->ep3_server) { if (!watched_lobby->ep3_server->battle_finished) { watched_lobby->ep3_server->send_commands_for_joining_spectator( - c->channel, c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); + c->channel, c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)); } send_ep3_update_game_metadata(watched_lobby); } @@ -3790,7 +3763,7 @@ static void on_99_GC(shared_ptr c, uint16_t, uint32_t, string& data) { // allows us to delay sending the 6xB4x52 until the server responds with a 99 // command after loading is done. auto l = c->lobby.lock(); - if (l && l->is_game() && (l->episode == Episode::EP3) && (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM)) { + if (l && l->is_game() && (l->episode == Episode::EP3) && l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { auto watched_l = l->watched_lobby.lock(); if (watched_l) { send_ep3_update_game_metadata(watched_l); @@ -3899,7 +3872,7 @@ static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, string& data) } static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& data) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Ep3 client sent card trade command"); } auto l = c->require_lobby(); @@ -3921,7 +3894,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat if (!target_c) { throw runtime_error("card trade command sent to missing player"); } - if (!(target_c->flags & Client::Flag::IS_EPISODE_3)) { + if (!target_c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("card trade target is not Episode 3"); } @@ -4003,7 +3976,7 @@ static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, string& dat static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Ep3 client sent card auction request"); } auto l = c->require_lobby(); @@ -4099,14 +4072,14 @@ static void on_04_P(shared_ptr c, uint16_t, uint32_t, string& data) { // On BB we can use colors and newlines should be \n; on PC we can't use // colors, the text is auto-word-wrapped, and newlines should be \r\n. - const string& message = (c->flags & Client::Flag::IS_BB_PATCH) + const string& message = c->config.check_flag(Client::Flag::IS_BB_PATCH) ? s->bb_patch_server_message : s->pc_patch_server_message; if (!message.empty()) { send_message_box(c, message.c_str()); } - auto index = (c->flags & Client::Flag::IS_BB_PATCH) ? s->bb_patch_file_index : s->pc_patch_file_index; + auto index = c->config.check_flag(Client::Flag::IS_BB_PATCH) ? s->bb_patch_file_index : s->pc_patch_file_index; if (index.get()) { send_command(c, 0x0B, 0x00); // Start patch session; go to root directory diff --git a/src/ReceiveCommands.hh b/src/ReceiveCommands.hh index 7618f39f..85003352 100644 --- a/src/ReceiveCommands.hh +++ b/src/ReceiveCommands.hh @@ -12,7 +12,6 @@ std::shared_ptr create_game_generic( Episode episode = Episode::EP1, GameMode mode = GameMode::NORMAL, uint8_t difficulty = 0, - uint32_t flags = 0, bool allow_v1 = false, std::shared_ptr watched_lobby = nullptr, std::shared_ptr battle_player = nullptr); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index c17af811..2c6f86cd 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -43,7 +43,7 @@ static void forward_subcommand( // If the command is an Ep3-only command, make sure an Ep3 client sent it bool command_is_ep3 = (command & 0xF0) == 0xC0; - if (command_is_ep3 && !(c->flags & Client::Flag::IS_EPISODE_3)) { + if (command_is_ep3 && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("Episode 3 command sent by non-Episode 3 client"); } @@ -61,7 +61,7 @@ static void forward_subcommand( } else { if (command_is_ep3) { for (auto& target : l->clients) { - if (!target || (target == c) || !(target->flags & Client::Flag::IS_EPISODE_3)) { + if (!target || (target == c) || !target->config.check_flag(Client::Flag::IS_EPISODE_3)) { continue; } send_command(target, command, flag, data, size); @@ -80,7 +80,7 @@ static void forward_subcommand( watcher_subcommands.count(subcommand)) { for (const auto& watcher_lobby : l->watcher_lobbies) { for (auto& target : watcher_lobby->clients) { - if (target && (target->flags & Client::Flag::IS_EPISODE_3)) { + if (target && target->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_command(target, command, flag, data, size); } } @@ -114,7 +114,7 @@ static void on_unimplemented(shared_ptr c, uint8_t command, uint8_t flag } else { c->log.warning("Unknown subcommand: %02hhX (public)", cmd.subcommand); } - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5Sub 6x%02hhX missing", cmd.subcommand); } } @@ -143,7 +143,7 @@ static void on_forward_sync_joining_player_state(shared_ptr c, uint8_t c throw runtime_error("compressed end offset is beyond end of command"); } - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string decompressed = bc0_decompress(reinterpret_cast(data) + sizeof(cmd), cmd.compressed_size); c->log.info("Decompressed sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):", cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load()); @@ -192,7 +192,7 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm } string decompressed = bc0_decompress(reinterpret_cast(data) + sizeof(cmd), cmd.compressed_size); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { c->log.info("Decompressed item sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):", cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load()); print_data(stderr, decompressed); @@ -235,7 +235,7 @@ static void on_sync_joining_player_item_state(shared_ptr c, uint8_t comm out_cmd.decompressed_size = decompressed.size(); out_cmd.compressed_size = out_compressed_data.size(); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { c->log.info("Byteswapped and recompressed item sync data (%zX bytes)", out_compressed_data.size()); } @@ -346,16 +346,16 @@ static void on_ep3_sound_chat(shared_ptr c, uint8_t command, uint8_t fla // forwarded from spectator teams to the primary team. The client only uses // this behavior for the 6xBE command (sound chat), and newserv enforces that // only that command is sent via CB. - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Episode 3 client sent sound chat command"); } auto l = c->require_lobby(); - if ((command == 0xCB) && (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM)) { + if ((command == 0xCB) && l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { auto watched_lobby = l->watched_lobby.lock(); if (watched_lobby) { for (auto& target : watched_lobby->clients) { - if (target && (target->flags & Client::Flag::IS_EPISODE_3)) { + if (target && target->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_command(target, command, flag, data, size); } } @@ -520,10 +520,10 @@ static void on_set_player_visibility(shared_ptr c, uint8_t command, uint auto l = c->require_lobby(); forward_subcommand(c, command, flag, data, size); - if (!l->is_game() && !(c->flags & Client::Flag::IS_DC_V1)) { + if (!l->is_game() && !c->config.check_flag(Client::Flag::IS_DC_V1)) { send_arrow_update(l); } - if (!l->is_game() && (l->flags & Lobby::Flag::IS_OVERFLOW)) { + if (!l->is_game() && l->check_flag(Lobby::Flag::IS_OVERFLOW)) { send_message_box(c, "$C6All lobbies are full.\n\n$C7You are in a private lobby. You can use the\nteleporter to join other lobbies if there is space\navailable."); send_lobby_message_box(c, ""); } @@ -549,7 +549,7 @@ static void on_player_died(shared_ptr c, uint8_t command, uint8_t flag, return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { try { auto& inventory = c->game_data.player()->inventory; size_t mag_index = inventory.find_equipped_mag(); @@ -569,7 +569,7 @@ static void on_hit_by_enemy(shared_ptr c, uint8_t command, uint8_t flag, auto l = c->require_lobby(); if (l->is_game() && (cmd.client_id == c->lobby_client_id)) { forward_subcommand(c, command, flag, data, size); - if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->options.infinite_hp) { + if (l->check_flag(Lobby::Flag::CHEATS_ENABLED) && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550); } } @@ -582,7 +582,7 @@ static void on_cast_technique_finished(shared_ptr c, uint8_t command, ui auto l = c->require_lobby(); if (l->is_game() && (cmd.header.client_id == c->lobby_client_id)) { forward_subcommand(c, command, flag, data, size); - if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->options.infinite_tp) { + if (l->check_flag(Lobby::Flag::CHEATS_ENABLED) && c->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) { send_player_stats_change(c, PlayerStatsChange::ADD_TP, 255); } } @@ -629,10 +629,11 @@ static void on_switch_state_changed(shared_ptr c, uint8_t command, uint8 forward_subcommand(c, command, flag, data, size); if (cmd.flags && cmd.header.object_id != 0xFFFF) { - if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->options.switch_assist && + if (l->check_flag(Lobby::Flag::CHEATS_ENABLED) && + c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) && (c->last_switch_enabled_command.header.subcommand == 0x05)) { c->log.info("[Switch assist] Replaying previous enable command"); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message(c, "$C5Switch assist"); } forward_subcommand(c, command, flag, &c->last_switch_enabled_command, sizeof(c->last_switch_enabled_command)); @@ -682,7 +683,7 @@ static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t f } auto l = c->require_lobby(); - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto p = c->game_data.player(); auto item = p->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB); l->add_item(item, cmd.area, cmd.x, cmd.z); @@ -691,7 +692,7 @@ static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t f auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { auto name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5DROP %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); @@ -738,7 +739,7 @@ static void on_create_inventory_item_t(shared_ptr c, uint8_t command, ui } auto l = c->require_lobby(); - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto p = c->game_data.player(); ItemData item = cmd.item_data; item.decode_if_mag(c->version()); @@ -748,7 +749,7 @@ static void on_create_inventory_item_t(shared_ptr c, uint8_t command, ui auto s = c->require_server_state(); auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)", c->lobby_client_id, item.id.load(), name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5CREATE %08" PRIX32 "\n%s", item.id.load(), name.c_str()); } @@ -781,7 +782,7 @@ static void on_drop_partial_stack_t(shared_ptr c, uint8_t command, uint8 return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { // TODO: Should we delete anything from the inventory here? Does the client // send an appropriate 6x29 alongside this? ItemData item = cmd.item_data; @@ -794,7 +795,7 @@ static void on_drop_partial_stack_t(shared_ptr c, uint8_t command, uint8 l->log.info("Player %hu split stack to create floor item %08" PRIX32 " (%s) at %hu:(%g, %g)", cmd.header.client_id.load(), item.id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5SPLIT %08" PRIX32 "\n%s", item.id.load(), name.c_str()); } @@ -823,7 +824,7 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint return; } - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -849,7 +850,7 @@ static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint l->log.info("Player %hu split stack %08" PRIX32 " (removed: %s) at %hu:(%g, %g)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { auto name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5SPLIT/BB %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); @@ -875,7 +876,7 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto p = c->game_data.player(); ItemData item = cmd.item_data; item.decode_if_mag(c->version()); @@ -886,7 +887,7 @@ static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop (%zu Meseta)", cmd.header.client_id.load(), item.id.load(), name.c_str(), price); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { auto name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5BUY %08" PRIX32 "\n%s", item.id.load(), name.c_str()); } @@ -915,7 +916,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr c, uint8_t command, u return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { ItemData item = cmd.item.item; item.decode_if_mag(c->version()); l->on_item_id_generated_externally(c->lobby_client_id, item.id); @@ -925,7 +926,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr c, uint8_t command, u auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hhu (leader) created floor item %08" PRIX32 " (%s) at %hhu:(%g, %g)", l->leader_id, item.id.load(), name.c_str(), cmd.item.area, cmd.item.x.load(), cmd.item.z.load()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5DROP %08" PRIX32 "\n%s", item.id.load(), name.c_str()); } @@ -971,7 +972,7 @@ static void on_pick_up_item(shared_ptr c, uint8_t command, uint8_t flag, return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto effective_p = effective_c->game_data.player(); auto item = l->remove_item(cmd.item_id); effective_p->add_item(item); @@ -980,7 +981,7 @@ static void on_pick_up_item(shared_ptr c, uint8_t command, uint8_t flag, auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hu picked up %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { auto name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5PICK %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); } @@ -1004,7 +1005,7 @@ static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8 // picked up. To account for this, we discard requests to pick up items that // don't exist instead of disconnecting the client. if (l->base_version == GameVersion::BB) { - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -1016,7 +1017,7 @@ static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8 auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hu picked up (BB) %08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { auto name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5PICK/BB %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); @@ -1025,7 +1026,7 @@ static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8 send_pick_up_item(c, cmd.item_id, cmd.area); - } else if ((l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) && !l->item_exists(cmd.item_id)) { + } else if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED) && !l->item_exists(cmd.item_id)) { l->log.warning("Player %hu requests to pick up %08" PRIX32 ", but the item does not exist; dropping command", cmd.header.client_id.load(), cmd.item_id.load()); @@ -1042,7 +1043,7 @@ static void on_equip_unequip_item(shared_ptr c, uint8_t command, uint8_t } auto l = c->require_lobby(); - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto p = c->game_data.player(); size_t index = p->inventory.find_item(cmd.item_id); if (cmd.header.subcommand == 0x25) { // Equip @@ -1069,7 +1070,7 @@ static void on_use_item( } auto l = c->require_lobby(); - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); auto p = c->game_data.player(); size_t index = p->inventory.find_item(cmd.item_id); @@ -1085,7 +1086,7 @@ static void on_use_item( l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)", c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5USE %08" PRIX32 "\n%s", cmd.item_id.load(), colored_name.c_str()); } @@ -1107,7 +1108,7 @@ static void on_feed_mag( } auto l = c->require_lobby(); - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); auto p = c->game_data.player(); @@ -1137,7 +1138,7 @@ static void on_feed_mag( l->log.info("Player %hhu fed item %hu:%08" PRIX32 " (%s) to mag %hu:%08" PRIX32 " (%s)", c->lobby_client_id, cmd.header.client_id.load(), cmd.fed_item_id.load(), fed_name.c_str(), cmd.header.client_id.load(), cmd.mag_item_id.load(), mag_name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5FEED %08" PRIX32 "\n%s\n...TO %08" PRIX32 "\n%s", cmd.fed_item_id.load(), fed_colored_name.c_str(), cmd.mag_item_id.load(), mag_colored_name.c_str()); @@ -1206,7 +1207,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint return; } - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -1244,7 +1245,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint } } - } else if ((c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3)) { + } else if ((c->version() == GameVersion::GC) && c->config.check_flag(Client::Flag::IS_EPISODE_3)) { forward_subcommand(c, command, flag, data, size); } } @@ -1254,7 +1255,7 @@ static void on_sort_inventory_bb(shared_ptr c, uint8_t, uint8_t, const v if (l->base_version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -1307,7 +1308,7 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u return; } - if (!(l->flags & Lobby::Flag::DROPS_ENABLED)) { + if (!l->check_flag(Lobby::Flag::DROPS_ENABLED)) { return; } if (!l->item_creator) { @@ -1354,7 +1355,7 @@ static void on_entity_drop_item_request(shared_ptr c, uint8_t command, u } item.id = l->generate_item_id(0xFF); - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { l->add_item(item, cmd.area, cmd.x, cmd.z); } send_drop_item(l, item, cmd.rt_index != 0x30, cmd.area, cmd.x, cmd.z, cmd.entity_id); @@ -1548,7 +1549,7 @@ static void on_steal_exp_bb(shared_ptr c, uint8_t, uint8_t, const void* // types, so we don't check for that here. uint32_t percent = special.amount + (char_class_is_android(p->disp.visual.char_class) ? 30 : 0); uint32_t stolen_exp = min((enemy_exp * percent) / 100, (l->difficulty + 1) * 20); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", stolen_exp, cmd.enemy_id.load(), name_for_enum(enemy.type)); } @@ -1582,7 +1583,7 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t command, uint8_t fl string e_str = e.str(); c->log.info("Enemy killed: E-%hX => %s", cmd.enemy_id.load(), e_str.c_str()); if (e.flags & Map::Enemy::Flag::DEFEATED) { - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5E-%hX __DEFEATED__", cmd.enemy_id.load()); } return; @@ -1594,7 +1595,7 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t command, uint8_t fl uint32_t bp_index = battle_param_index_for_enemy_type(l->episode, e.type); experience = bp_table.stats[l->difficulty][bp_index].experience * l->exp_multiplier; } catch (const exception& e) { - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5E-%hX __MISSING__\n%s", cmd.enemy_id.load(), e.what()); } else { send_text_message_printf(c, "$C4Unknown enemy type killed:\n%s", e.what()); @@ -1619,7 +1620,7 @@ static void on_enemy_killed_bb(shared_ptr c, uint8_t command, uint8_t fl // Killer gets full experience, others get 77% bool is_killer = (e.last_hit_by_client_id == other_c->lobby_client_id); uint32_t player_exp = is_killer ? experience : ((experience * 77) / 100); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", player_exp, cmd.enemy_id.load(), name_for_enum(e.type)); } @@ -1682,14 +1683,14 @@ static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uin return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); auto p = c->game_data.player(); auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB); auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)", c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5DESTROY %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); @@ -1707,13 +1708,13 @@ static void on_destroy_ground_item(shared_ptr c, uint8_t command, uint8_ return; } - if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { + if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { auto s = c->require_server_state(); auto item = l->remove_item(cmd.item_id); auto name = s->describe_item(c->version(), item, false); l->log.info("Player %hhu destroyed floor item %08" PRIX32 " (%s)", c->lobby_client_id, cmd.item_id.load(), name.c_str()); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5DESTROY/GND %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); @@ -1729,7 +1730,7 @@ static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t f if (!l->is_game()) { return; } - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } if (!l->item_creator.get()) { @@ -1758,7 +1759,7 @@ static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, ui if (l->base_version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -1783,7 +1784,7 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 if (l->base_version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -1797,7 +1798,7 @@ static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8 l->log.info("Player %hhu sold inventory item %08" PRIX32 " (%s) for %zu Meseta", c->lobby_client_id, cmd.item_id.load(), name.c_str(), price); p->print_inventory(stderr, c->version(), s->item_name_index); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5DESTROY/SELL %08" PRIX32 "\n+%zu Meseta\n%s", cmd.item_id.load(), price, name.c_str()); @@ -1811,7 +1812,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo auto l = c->require_lobby(); if (l->base_version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); - if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { throw logic_error("item tracking not enabled in BB game"); } @@ -1837,7 +1838,7 @@ static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const vo l->log.info("Player %hhu purchased item %08" PRIX32 " (%s) for %zu meseta", c->lobby_client_id, cmd.inventory_item_id.load(), name.c_str(), price); p->print_inventory(stderr, c->version(), s->item_name_index); - if (c->options.debug) { + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string name = s->describe_item(c->version(), item, true); send_text_message_printf(c, "$C5CREATE/BUY %08" PRIX32 "\n-%zu Meseta\n%s", cmd.inventory_item_id.load(), price, name.c_str()); @@ -1858,7 +1859,7 @@ static void on_battle_restart_bb(shared_ptr c, uint8_t, uint8_t, const v if (l->is_game() && (l->base_version == GameVersion::BB) && (l->mode == GameMode::BATTLE) && - (l->flags & Lobby::Flag::QUEST_IN_PROGRESS)) { + l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); shared_ptr new_rules(new BattleRules(cmd.rules)); @@ -1884,7 +1885,7 @@ static void on_battle_level_up_bb(shared_ptr c, uint8_t, uint8_t, const if (l->is_game() && (l->base_version == GameVersion::BB) && (l->mode == GameMode::BATTLE) && - (l->flags & Lobby::Flag::QUEST_IN_PROGRESS)) { + l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto lc = l->clients[cmd.header.client_id]; if (lc) { @@ -1903,7 +1904,7 @@ static void on_quest_exchange_item_bb(shared_ptr c, uint8_t, uint8_t, co auto l = c->require_lobby(); if (l->is_game() && (l->base_version == GameVersion::BB) && - (l->flags & Lobby::Flag::QUEST_IN_PROGRESS)) { + l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); try { @@ -1973,7 +1974,7 @@ static void on_photon_drop_exchange_bb(shared_ptr c, uint8_t, uint8_t, c static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && (l->flags & Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { check_size_t(data, size); auto p = c->game_data.player(); size_t index = p->inventory.find_item_by_primary_identifier(0x031002); @@ -1985,7 +1986,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr c, uint8_t, uint8_t static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && (l->flags & Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto p = c->game_data.player(); try { @@ -2014,7 +2015,7 @@ static void on_momoka_item_exchange_bb(shared_ptr c, uint8_t, uint8_t, c static void on_upgrade_weapon_attribute_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { auto l = c->require_lobby(); - if (l->is_game() && (l->base_version == GameVersion::BB) && (l->flags & Lobby::Flag::QUEST_IN_PROGRESS)) { + if (l->is_game() && (l->base_version == GameVersion::BB) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { const auto& cmd = check_size_t(data, size); auto p = c->game_data.player(); try { @@ -2338,7 +2339,8 @@ void on_subcommand_multi(shared_ptr c, uint8_t command, uint8_t flag, co if (data.empty()) { throw runtime_error("game command is empty"); } - if (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE))) { + if (c->version() == GameVersion::DC && + (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE))) { // TODO: We should convert these to non-trial formats and vice versa forward_subcommand(c, command, flag, data.data(), data.size()); } else { diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 6a5904da..b002020a 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -84,7 +84,7 @@ void send_command_excluding_client(shared_ptr l, shared_ptr c, void send_command_if_not_loading(shared_ptr l, uint16_t command, uint32_t flag, const void* data, size_t size) { for (auto& client : l->clients) { - if (!client || (client->flags & Client::Flag::LOADING)) { + if (!client || client->config.check_flag(Client::Flag::LOADING)) { continue; } send_command(client, command, flag, data, size); @@ -246,11 +246,31 @@ void send_server_init(shared_ptr c, uint8_t flags) { } void send_update_client_config(shared_ptr c) { - S_UpdateClientConfig_DC_PC_V3_04 cmd; - cmd.player_tag = 0x00010000; - cmd.guild_card_number = c->license->serial_number; - cmd.cfg = c->export_config(); - send_command_t(c, 0x04, 0x00, cmd); + switch (c->version()) { + case GameVersion::DC: + case GameVersion::PC: { + if (!c->config.check_flag(Client::Flag::HAS_GUILD_CARD_NUMBER)) { + c->config.set_flag(Client::Flag::HAS_GUILD_CARD_NUMBER); + S_UpdateClientConfig_DC_PC_04 cmd; + cmd.player_tag = 0x00010000; + cmd.guild_card_number = c->license->serial_number; + send_command_t(c, 0x04, 0x00, cmd); + } + break; + } + case GameVersion::GC: + case GameVersion::XB: { + c->config.set_flag(Client::Flag::HAS_GUILD_CARD_NUMBER); + S_UpdateClientConfig_V3_04 cmd; + cmd.player_tag = 0x00010000; + cmd.guild_card_number = c->license->serial_number; + c->config.serialize_into(cmd.client_config); + send_command_t(c, 0x04, 0x00, cmd); + break; + } + default: + throw logic_error("send_update_client_config called on incorrect game version"); + } } template @@ -325,15 +345,15 @@ void prepare_client_for_patches(shared_ptr c, std::function on_c return; } if (c->version() == GameVersion::GC && - c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + c->config.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect")); c->function_call_response_queue.emplace_back([wc = weak_ptr(c), on_complete](uint32_t specific_version, uint32_t) -> void { auto c = wc.lock(); if (!c) { return; } - c->specific_version = specific_version; - c->log.info("Version detected as %08" PRIX32, c->specific_version); + c->config.specific_version = specific_version; + c->log.info("Version detected as %08" PRIX32, c->config.specific_version); on_complete(); }); } else { @@ -341,7 +361,7 @@ void prepare_client_for_patches(shared_ptr c, std::function on_c } }; - if (!(c->flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { + if (!c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { send_function_call(c, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0x80000000, 8, 0x7F2734EC); c->function_call_response_queue.emplace_back([s, wc = weak_ptr(c), send_version_detect](uint32_t, uint32_t header_checksum) -> void { auto c = wc.lock(); @@ -349,8 +369,8 @@ void prepare_client_for_patches(shared_ptr c, std::function on_c return; } try { - c->specific_version = specific_version_for_gc_header_checksum(header_checksum); - c->log.info("Version detected as %08" PRIX32 " from header checksum %08" PRIX32, c->specific_version, header_checksum); + c->config.specific_version = specific_version_for_gc_header_checksum(header_checksum); + c->log.info("Version detected as %08" PRIX32 " from header checksum %08" PRIX32, c->config.specific_version, header_checksum); } catch (const out_of_range&) { c->log.info("Could not detect specific version from header checksum %08" PRIX32, header_checksum); } @@ -361,7 +381,7 @@ void prepare_client_for_patches(shared_ptr c, std::function on_c return; } c->log.info("Client cache behavior patched"); - c->flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; + c->config.set_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); send_update_client_config(c); send_version_detect(); }); @@ -381,7 +401,7 @@ void send_function_call( uint32_t override_relocations_offset) { return send_function_call( c->channel, - c->flags, + c->config, code, label_writes, suffix, @@ -392,17 +412,17 @@ void send_function_call( void send_function_call( Channel& ch, - uint64_t client_flags, + const Client::Config& client_config, shared_ptr code, const unordered_map& label_writes, const string& suffix, uint32_t checksum_addr, uint32_t checksum_size, uint32_t override_relocations_offset) { - if (client_flags & Client::Flag::NO_SEND_FUNCTION_CALL) { + if (client_config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) { throw logic_error("client does not support function calls"); } - if (code.get() && (client_flags & Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) { + if (code.get() && client_config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) { throw logic_error("client only supports checksums in send_function_call"); } @@ -412,7 +432,7 @@ void send_function_call( data = code->generate_client_command(label_writes, suffix, override_relocations_offset); index = code->index; - if (client_flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) { + if (client_config.check_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL)) { uint32_t key = random_object(); // This format was probably never used on any little-endian system, but we @@ -469,13 +489,13 @@ void send_pc_console_split_reconnect(shared_ptr c, uint32_t address, send_command_t(c, 0x19, 0x00, cmd); } -void send_client_init_bb(shared_ptr c, uint32_t error) { +void send_client_init_bb(shared_ptr c, uint32_t error_code) { S_ClientInit_BB_00E6 cmd; - cmd.error = error; + cmd.error_code = error_code; cmd.player_tag = 0x00010000; cmd.guild_card_number = c->license->serial_number; cmd.team_id = static_cast(random_object()); - cmd.cfg = c->export_config_bb(); + c->config.serialize_into(cmd.client_config); cmd.can_create_team = 1; cmd.episode_4_unlocked = 1; send_command_t(c, 0x00E6, 0x00000000, cmd); @@ -744,11 +764,11 @@ void send_message_box(shared_ptr c, const string& text) { default: throw logic_error("invalid game version"); } - send_text(c->channel, command, text, (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + send_text(c->channel, command, text, c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); } void send_ep3_timed_message_box(Channel& ch, uint32_t frames, const string& message) { - string encoded = tt_encode_marked(message, ch.language, false); + string encoded = tt_encode_marked(add_color(message), ch.language, false); StringWriter w; w.put({frames}); w.write(encoded); @@ -766,15 +786,15 @@ void send_lobby_name(shared_ptr c, const string& text) { void send_quest_info(shared_ptr c, const string& text, bool is_download_quest) { send_text( c->channel, is_download_quest ? 0xA5 : 0xA3, text, - (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); } void send_lobby_message_box(shared_ptr c, const string& text) { - send_header_text(c->channel, 0x01, 0, text, (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + send_header_text(c->channel, 0x01, 0, text, c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); } void send_ship_info(shared_ptr c, const string& text) { - send_header_text(c->channel, 0x11, 0, text, (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); + send_header_text(c->channel, 0x11, 0, text, c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) ? ColorMode::STRIP : ColorMode::ADD); } void send_ship_info(Channel& ch, const string& text) { @@ -786,7 +806,7 @@ void send_text_message(Channel& ch, const string& text) { } void send_text_message(shared_ptr c, const string& text) { - if (!(c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { + if (!c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { send_header_text(c->channel, 0xB0, 0, text, ColorMode::ADD); } } @@ -814,7 +834,7 @@ __attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(shared_p va_end(va); for (auto& it : s->id_to_lobby) { for (auto& c : it.second->clients) { - if (c && (c->flags & Client::Flag::IS_EPISODE_3)) { + if (c && c->config.check_flag(Client::Flag::IS_EPISODE_3)) { send_text_message(c, buf); } } @@ -896,7 +916,7 @@ void send_prepared_chat_message(shared_ptr l, uint32_t from_guild_card_nu void send_chat_message(shared_ptr c, uint32_t from_guild_card_number, const string& from_name, const string& text, char private_flags) { string prepared_data = prepare_chat_data( c->version(), - c->flags & Client::Flag::IS_DC_TRIAL_EDITION, + c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION), c->language(), c->lobby_client_id, from_name, @@ -1143,7 +1163,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info switch (c->version()) { case GameVersion::DC: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DC); - if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_DCNTE); } break; @@ -1152,7 +1172,7 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info break; case GameVersion::GC: is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC); - if (c->flags & Client::Flag::IS_GC_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { is_visible &= !(item.flags & MenuItem::Flag::INVISIBLE_ON_GC_TRIAL_EDITION); } break; @@ -1166,13 +1186,13 @@ void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info throw runtime_error("menus not supported for this game version"); } if (item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) { - is_visible &= !(c->flags & Client::Flag::NO_D6); + is_visible &= !c->config.check_flag(Client::Flag::NO_D6); } if (item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) { - is_visible &= !(c->flags & Client::Flag::NO_SEND_FUNCTION_CALL); + is_visible &= !c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL); } if (item.flags & MenuItem::Flag::REQUIRES_SAVE_DISABLED) { - is_visible &= !(c->flags & Client::Flag::SAVE_ENABLED); + is_visible &= !c->config.check_flag(Client::Flag::SAVE_ENABLED); } if (item.flags & MenuItem::Flag::INVISIBLE_IN_INFO_MENU) { is_visible &= !is_info_menu; @@ -1227,8 +1247,7 @@ void send_game_menu_t( if (!l->version_is_allowed(c->quest_version())) { continue; } - bool l_is_spectator_team = !!(l->flags & Lobby::Flag::IS_SPECTATOR_TEAM); - if (l_is_spectator_team != is_spectator_team_list) { + if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) != is_spectator_team_list) { continue; } if (show_tournaments_only && !l->tournament_match) { @@ -1264,7 +1283,7 @@ void send_game_menu_t( e.episode = ((c->version() == GameVersion::BB) ? (l->max_clients << 4) : 0) | episode_num; } if (l->is_ep3()) { - e.flags = (l->password.empty() ? 0 : 2) | ((l->flags & Lobby::Flag::BATTLE_IN_PROGRESS) ? 4 : 0); + e.flags = (l->password.empty() ? 0 : 2) | (l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS) ? 4 : 0); } else { e.flags = ((episode_num << 6) | (l->password.empty() ? 0 : 2)); switch (l->mode) { @@ -1396,13 +1415,13 @@ void send_lobby_list(shared_ptr c) { auto s = c->require_server_state(); vector entries; for (shared_ptr l : s->all_lobbies()) { - if (!(l->flags & Lobby::Flag::DEFAULT)) { + if (!l->check_flag(Lobby::Flag::DEFAULT)) { continue; } - if ((l->flags & Lobby::Flag::V2_AND_LATER) && (c->flags & Client::Flag::IS_DC_V1)) { + if (l->check_flag(Lobby::Flag::V2_AND_LATER) && c->config.check_flag(Client::Flag::IS_DC_V1)) { continue; } - if (l->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) { + if (l->is_ep3() && !c->config.check_flag(Client::Flag::IS_EPISODE_3)) { continue; } auto& e = entries.emplace_back(); @@ -1442,13 +1461,13 @@ void send_player_records_t(shared_ptr c, shared_ptr l, shared_ptr } static void send_join_spectator_team(shared_ptr c, shared_ptr l) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("lobby is not Episode 3"); } if (!l->is_ep3()) { throw runtime_error("lobby is not Episode 3"); } - if (!(l->flags & Lobby::Flag::IS_SPECTATOR_TEAM)) { + if (!l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { throw runtime_error("lobby is not a spectator team"); } @@ -1474,7 +1493,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto wc_p = wc->game_data.player(); auto& p = cmd.players[z]; p.lobby_data.player_tag = 0x00010000; - p.lobby_data.guild_card = wc->license->serial_number; + p.lobby_data.guild_card_number = wc->license->serial_number; p.lobby_data.client_id = wc->lobby_client_id; p.lobby_data.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language()); p.inventory = wc_p->inventory; @@ -1522,7 +1541,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto& e = cmd.entries[client_id]; e.player_tag = 0x00010000; - e.guild_card_number = entry.lobby_data.guild_card; + e.guild_card_number = entry.lobby_data.guild_card_number; e.name = entry.disp.visual.name; e.present = 1; e.level = entry.level.load(); @@ -1542,7 +1561,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto& cmd_p = cmd.spectator_players[z - 4]; auto& cmd_e = cmd.entries[z]; cmd_p.lobby_data.player_tag = 0x00010000; - cmd_p.lobby_data.guild_card = other_c->license->serial_number; + cmd_p.lobby_data.guild_card_number = other_c->license->serial_number; cmd_p.lobby_data.client_id = other_c->lobby_client_id; cmd_p.lobby_data.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language()); cmd_p.inventory = other_p->inventory; @@ -1567,7 +1586,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) } void send_join_game(shared_ptr c, shared_ptr l) { - if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { + if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { send_join_spectator_team(c, l); return; } @@ -1578,7 +1597,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { auto lc = l->clients[x]; if (lc) { cmd.lobby_data[x].player_tag = 0x00010000; - cmd.lobby_data[x].guild_card = lc->license->serial_number; + cmd.lobby_data[x].guild_card_number = lc->license->serial_number; cmd.lobby_data[x].client_id = lc->lobby_client_id; cmd.lobby_data[x].name.encode(lc->game_data.player()->disp.name.decode(lc->language()), c->language()); player_count++; @@ -1623,7 +1642,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { switch (c->version()) { case GameVersion::DC: { - if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { S_JoinGame_DCNTE_64 cmd; cmd.client_id = c->lobby_client_id; cmd.leader_id = l->leader_id; @@ -1645,7 +1664,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { break; } case GameVersion::GC: { - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { S_JoinGame_GC_Ep3_64 cmd; size_t player_count = populate_v3_cmd(cmd); for (size_t x = 0; x < 4; x++) { @@ -1692,7 +1711,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptris_game()) { if (joining_client) { - command = (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 0xEB : 0x65; + command = l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 0xEB : 0x65; } else { throw logic_error("send_join_lobby_t should not be used for primary game join command"); } @@ -1700,15 +1719,15 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrversion() != GameVersion::DC) || !(c->flags & Client::Flag::IS_DC_V1)) { + if ((c->version() != GameVersion::DC) || !c->config.check_flag(Client::Flag::IS_DC_V1)) { send_player_records_t(c, l, joining_client); } uint8_t lobby_type; - if (c->options.override_lobby_number >= 0) { - lobby_type = c->options.override_lobby_number; - } else if (l->flags & Lobby::Flag::IS_OVERFLOW) { - lobby_type = (c->flags & Client::Flag::IS_EPISODE_3) ? 15 : 0; + if (c->config.override_lobby_number != 0x80) { + lobby_type = c->config.override_lobby_number; + } else if (l->check_flag(Lobby::Flag::IS_OVERFLOW)) { + lobby_type = c->config.check_flag(Client::Flag::IS_EPISODE_3) ? 15 : 0; } else { lobby_type = l->block - 1; } @@ -1716,7 +1735,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrversion() == GameVersion::GC) { - if (c->flags & Client::Flag::IS_EPISODE_3) { + if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { if ((lobby_type > 0x14) && (lobby_type < 0xE9)) { lobby_type = l->block - 1; } @@ -1758,7 +1777,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrgame_data.player(); auto& e = cmd.entries[used_entries++]; e.lobby_data.player_tag = 0x00010000; - e.lobby_data.guild_card = lc->license->serial_number; + e.lobby_data.guild_card_number = lc->license->serial_number; e.lobby_data.client_id = lc->lobby_client_id; if (UseLanguageMarkerInName) { e.lobby_data.name.encode("\tJ" + lp->disp.name.decode(lp->inventory.language), c->language()); @@ -1808,7 +1827,7 @@ void send_join_lobby_dc_nte(shared_ptr c, shared_ptr l, auto lp = lc->game_data.player(); auto& e = cmd.entries[used_entries++]; e.lobby_data.player_tag = 0x00010000; - e.lobby_data.guild_card = lc->license->serial_number; + e.lobby_data.guild_card_number = lc->license->serial_number; e.lobby_data.client_id = lc->lobby_client_id; e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language()); e.inventory = lp->inventory; @@ -1826,7 +1845,7 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { } else { switch (c->version()) { case GameVersion::DC: - if (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { + if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE)) { send_join_lobby_dc_nte(c, l); } else { send_join_lobby_t(c, l); @@ -1851,8 +1870,8 @@ void send_join_lobby(shared_ptr c, shared_ptr l) { // If the client will stop sending message box close confirmations after // joining any lobby, set the appropriate flag and update the client config - if ((c->flags & (Client::Flag::NO_D6_AFTER_LOBBY | Client::Flag::NO_D6)) == Client::Flag::NO_D6_AFTER_LOBBY) { - c->flags |= Client::Flag::NO_D6; + if (c->config.check_flag(Client::Flag::NO_D6_AFTER_LOBBY) && !c->config.check_flag(Client::Flag::NO_D6)) { + c->config.set_flag(Client::Flag::NO_D6); send_update_client_config(c); } } @@ -1861,7 +1880,7 @@ void send_player_join_notification(shared_ptr c, shared_ptr l, shared_ptr joining_client) { switch (c->version()) { case GameVersion::DC: - if (c->flags & (Client::Flag::IS_DC_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)) { + if (c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION) || c->config.check_flag(Client::Flag::IS_DC_V1_PROTOTYPE)) { send_join_lobby_dc_nte(c, l, joining_client); } else { send_join_lobby_t(c, l, joining_client); @@ -1888,7 +1907,7 @@ void send_player_leave_notification(shared_ptr l, uint8_t leaving_client_ S_LeaveLobby_66_69_Ep3_E9 cmd = {leaving_client_id, l->leader_id, 1, 0}; uint8_t cmd_num; if (l->is_game()) { - cmd_num = (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) ? 0xE9 : 0x66; + cmd_num = l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 0xE9 : 0x66; } else { cmd_num = 0x69; } @@ -1905,7 +1924,7 @@ void send_self_leave_notification(shared_ptr c) { void send_get_player_info(shared_ptr c) { if ((c->version() == GameVersion::DC) && - (c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { + c->config.check_flag(Client::Flag::IS_DC_TRIAL_EDITION)) { send_command(c, 0x8D, 0x00); } else { send_command(c, 0x95, 0x00); @@ -1931,7 +1950,7 @@ void send_execute_item_trade(shared_ptr c, const vector& items void send_execute_card_trade(shared_ptr c, const vector>& card_to_count) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw logic_error("cannot send trade cards command to non-Ep3 client"); } @@ -1972,7 +1991,7 @@ void send_arrow_update(shared_ptr l) { } for (size_t x = 0; x < l->max_clients; x++) { - if (!l->clients[x] || (l->clients[x]->flags & Client::Flag::IS_DC_V1)) { + if (!l->clients[x] || l->clients[x]->config.check_flag(Client::Flag::IS_DC_V1)) { continue; } send_command_vt(l->clients[x], 0x88, entries.size(), entries); @@ -2233,9 +2252,9 @@ void send_quest_function_call(shared_ptr c, uint16_t function_id) { // ep3 only commands void send_ep3_card_list_update(shared_ptr c) { - if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) { + if (!c->config.check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) { auto s = c->require_server_state(); - const auto& data = (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) + const auto& data = c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION) ? s->ep3_card_index_trial->get_compressed_definitions() : s->ep3_card_index->get_compressed_definitions(); @@ -2309,7 +2328,7 @@ void send_ep3_set_context_token(shared_ptr c, uint32_t context_token) { void send_ep3_confirm_tournament_entry( shared_ptr c, shared_ptr tourn) { - if (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) { + if (c->config.check_flag(Client::Flag::IS_EP3_TRIAL_EDITION)) { throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client"); } @@ -2426,7 +2445,7 @@ void send_ep3_tournament_details( } string ep3_description_for_client(shared_ptr c) { - if (!(c->flags & Client::Flag::IS_EPISODE_3)) { + if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("client is not Episode 3"); } auto player = c->game_data.player(); @@ -2440,7 +2459,7 @@ string ep3_description_for_client(shared_ptr c) { void send_ep3_game_details(shared_ptr c, shared_ptr l) { shared_ptr primary_lobby; - if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { + if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { primary_lobby = l->watched_lobby.lock(); } else { primary_lobby = l; @@ -2852,7 +2871,7 @@ bool send_quest_barrier_if_all_clients_ready(shared_ptr l) { if (!l->clients[x]) { continue; } - if (l->clients[x]->flags & Client::Flag::LOADING_QUEST) { + if (l->clients[x]->config.check_flag(Client::Flag::LOADING_QUEST)) { break; } } @@ -2886,7 +2905,7 @@ bool send_ep3_start_tournament_deck_select_if_all_clients_ready(shared_ptrclients[x]) { continue; } - if (l->clients[x]->flags & Client::Flag::LOADING_TOURNAMENT) { + if (l->clients[x]->config.check_flag(Client::Flag::LOADING_TOURNAMENT)) { break; } } @@ -2930,7 +2949,7 @@ void send_ep3_card_auction(shared_ptr l) { distribution_size += e.probability; } - auto card_index = (l->flags & Lobby::Flag::IS_EP3_TRIAL) + auto card_index = l->check_flag(Lobby::Flag::IS_EP3_TRIAL) ? s->ep3_card_index_trial : s->ep3_card_index; @@ -2983,7 +3002,7 @@ void send_change_event(shared_ptr c, uint8_t new_event) { // This command isn't supported on versions before V3, nor on Trial Edition. if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC) || - (c->flags & Client::Flag::IS_GC_TRIAL_EDITION)) { + c->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION)) { return; } send_command(c, 0xDA, new_event); diff --git a/src/SendCommands.hh b/src/SendCommands.hh index f82e8992..f5620404 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -136,7 +136,7 @@ void send_quest_buffer_overflow(std::shared_ptr c); void prepare_client_for_patches(std::shared_ptr c, std::function on_complete); void send_function_call( Channel& ch, - uint64_t client_flags, + const Client::Config& client_config, std::shared_ptr code, const std::unordered_map& label_writes = {}, const std::string& suffix = "", diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 6d7c0280..ec98bbef 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -45,6 +45,16 @@ static void set_boolean(bool* target, const string& args) { } } +static void set_flag(Client::Config& config, Client::Flag flag, const string& args) { + if (args == "on") { + config.set_flag(flag); + } else if (args == "off") { + config.clear_flag(flag); + } else { + throw invalid_argument("argument must be \"on\" or \"off\""); + } +} + static string get_quoted_string(string& s) { string ret; char end_char = (s.at(0) == '\"') ? '\"' : ' '; @@ -346,7 +356,7 @@ Proxy session commands:\n\ } else if (mask == "root") { l->flags = License::Flag::ROOT; } else { - l->flags = stoul(mask); + l->flags = stoul(mask, nullptr, 16); } } else { @@ -403,7 +413,7 @@ Proxy session commands:\n\ l->serial_number = stoul(token.substr(7)); } else if (starts_with(token, "flags=")) { - string mask = token.substr(11); + string mask = token.substr(6); if (mask == "normal") { l->flags = 0; } else if (mask == "mod") { @@ -413,7 +423,7 @@ Proxy session commands:\n\ } else if (mask == "root") { l->flags = License::Flag::ROOT; } else { - l->flags = stoul(mask); + l->flags = stoul(mask, nullptr, 16); } } else { @@ -695,7 +705,7 @@ Proxy session commands:\n\ } else if ((command_name == "wc") || (command_name == "wchat")) { auto session = this->get_proxy_session(session_name); if ((session->version() != GameVersion::GC) || - !(session->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + !session->config.check_flag(Client::Flag::IS_EPISODE_3)) { throw runtime_error("wchat can only be used on Episode 3"); } string data(8, '\0'); @@ -741,29 +751,30 @@ Proxy session commands:\n\ } else if (command_name == "set-override-section-id") { auto session = this->get_proxy_session(session_name); if (command_args.empty()) { - session->options.override_section_id = -1; + session->config.override_section_id = 0xFF; } else { - session->options.override_section_id = section_id_for_name(command_args); + session->config.override_section_id = section_id_for_name(command_args); } } else if (command_name == "set-override-event") { auto session = this->get_proxy_session(session_name); if (command_args.empty()) { - session->options.override_lobby_event = -1; + session->config.override_lobby_event = 0xFF; } else { - session->options.override_lobby_event = event_for_name(command_args); + session->config.override_lobby_event = event_for_name(command_args); if ((session->version() != GameVersion::DC) && - (session->version() != GameVersion::PC) && (!((session->version() == GameVersion::GC) && (session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)))) { - session->client_channel.send(0xDA, session->options.override_lobby_event); + (session->version() != GameVersion::PC) && + !((session->version() == GameVersion::GC) && session->config.check_flag(Client::Flag::IS_GC_TRIAL_EDITION))) { + session->client_channel.send(0xDA, session->config.override_lobby_event); } } } else if (command_name == "set-override-lobby-number") { auto session = this->get_proxy_session(session_name); if (command_args.empty()) { - session->options.override_lobby_number = -1; + session->config.override_lobby_number = 0x80; } else { - session->options.override_lobby_number = lobby_type_for_name(command_args); + session->config.override_lobby_number = lobby_type_for_name(command_args); } } else if (command_name == "set-challenge-rank-title") { @@ -776,31 +787,27 @@ Proxy session commands:\n\ } else if (command_name == "set-chat-filter") { auto session = this->get_proxy_session(session_name); - set_boolean(&session->options.enable_chat_filter, command_args); + set_flag(session->config, Client::Flag::PROXY_CHAT_FILTER_ENABLED, command_args); } else if (command_name == "set-infinite-hp") { auto session = this->get_proxy_session(session_name); - set_boolean(&session->options.infinite_hp, command_args); + set_flag(session->config, Client::Flag::INFINITE_HP_ENABLED, command_args); } else if (command_name == "set-infinite-tp") { auto session = this->get_proxy_session(session_name); - set_boolean(&session->options.infinite_tp, command_args); + set_flag(session->config, Client::Flag::INFINITE_TP_ENABLED, command_args); } else if (command_name == "set-switch-assist") { auto session = this->get_proxy_session(session_name); - set_boolean(&session->options.switch_assist, command_args); + set_flag(session->config, Client::Flag::SWITCH_ASSIST_ENABLED, command_args); } else if (command_name == "set-save-files" && this->state->proxy_allow_save_files) { auto session = this->get_proxy_session(session_name); - set_boolean(&session->options.save_files, command_args); + set_flag(session->config, Client::Flag::PROXY_SAVE_FILES, command_args); } else if (command_name == "set-block-function-calls") { auto session = this->get_proxy_session(session_name); - if (command_args.empty()) { - session->options.function_call_return_value = -1; - } else { - session->options.function_call_return_value = stoul(command_args); - } + set_flag(session->config, Client::Flag::PROXY_BLOCK_FUNCTION_CALLS, command_args); } else if ((command_name == "create-item") || (command_name == "set-next-item")) { auto session = this->get_proxy_session(session_name); diff --git a/src/ServerState.cc b/src/ServerState.cc index 43ce03b7..3ee36d9e 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -60,11 +60,12 @@ void ServerState::init() { bool is_ep3_only = (x > 14); shared_ptr l = this->create_lobby(); - l->flags |= - Lobby::Flag::PUBLIC | - Lobby::Flag::DEFAULT | - Lobby::Flag::PERSISTENT | - (v2_and_later_only ? Lobby::Flag::V2_AND_LATER : 0); + l->set_flag(Lobby::Flag::PUBLIC); + l->set_flag(Lobby::Flag::DEFAULT); + l->set_flag(Lobby::Flag::PERSISTENT); + if (v2_and_later_only) { + l->set_flag(Lobby::Flag::V2_AND_LATER); + } l->block = x + 1; l->name = lobby_name; l->max_clients = 12; @@ -122,8 +123,8 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { auto l = this->find_lobby(c->preferred_lobby_id); if (l && !l->is_game() && - (l->flags & Lobby::Flag::PUBLIC) && - ((c->flags & Client::Flag::IS_EPISODE_3) || (l->episode != Episode::EP3))) { + l->check_flag(Lobby::Flag::PUBLIC) && + (c->config.check_flag(Client::Flag::IS_EPISODE_3) || (l->episode != Episode::EP3))) { l->add_client(c); added_to_lobby = l; } @@ -133,9 +134,9 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { if (!added_to_lobby.get()) { const auto* search_order = &this->public_lobby_search_order_non_v1; - if (c->flags & Client::Flag::IS_DC_V1) { + if (c->config.check_flag(Client::Flag::IS_DC_V1)) { search_order = &this->public_lobby_search_order_v1; - } else if (c->flags & Client::Flag::IS_EPISODE_3) { + } else if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) { search_order = &this->public_lobby_search_order_ep3; } for (const auto& l : *search_order) { @@ -150,7 +151,8 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { if (!added_to_lobby) { added_to_lobby = this->create_lobby(); - added_to_lobby->flags |= Lobby::Flag::PUBLIC | Lobby::Flag::IS_OVERFLOW; + added_to_lobby->set_flag(Lobby::Flag::PUBLIC); + added_to_lobby->set_flag(Lobby::Flag::IS_OVERFLOW); added_to_lobby->block = 100; added_to_lobby->name = "Overflow"; added_to_lobby->max_clients = 12; @@ -166,7 +168,7 @@ void ServerState::remove_client_from_lobby(shared_ptr c) { auto l = c->lobby.lock(); if (l) { l->remove_client(c); - if (!(l->flags & Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) { + if (!l->check_flag(Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) { this->remove_lobby(l->lobby_id); } else { send_player_leave_notification(l, c->lobby_client_id); @@ -193,7 +195,7 @@ bool ServerState::change_client_lobby( } if (current_lobby) { - if (!(current_lobby->flags & Lobby::Flag::PERSISTENT) && (current_lobby->count_clients() == 0)) { + if (!current_lobby->check_flag(Lobby::Flag::PERSISTENT) && (current_lobby->count_clients() == 0)) { this->remove_lobby(current_lobby->lobby_id); } else { send_player_leave_notification(current_lobby, old_lobby_client_id); @@ -263,7 +265,7 @@ void ServerState::remove_lobby(uint32_t lobby_id) { throw logic_error("attempted to delete lobby with clients in it"); } - if (l->flags & Lobby::Flag::IS_SPECTATOR_TEAM) { + if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { auto primary_l = l->watched_lobby.lock(); if (primary_l) { primary_l->log.info("Unlinking watcher lobby %" PRIX32, l->lobby_id); @@ -1153,7 +1155,7 @@ shared_ptr> ServerState::information_contents_for_client(sh } shared_ptr ServerState::quest_index_for_client(shared_ptr c) const { - return (c->flags & Client::Flag::IS_EPISODE_3) + return c->config.check_flag(Client::Flag::IS_EPISODE_3) ? this->ep3_download_quest_index : this->default_quest_index; } diff --git a/src/Version.cc b/src/Version.cc index e9bb122f..dc0b1184 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -15,98 +15,6 @@ const vector version_to_lobby_port_name = { const vector version_to_proxy_port_name = { "", "dc-proxy", "pc-proxy", "gc-proxy", "xb-proxy", "bb-proxy"}; -uint32_t flags_for_version(GameVersion version, int64_t sub_version) { - switch (sub_version) { - case -1: // Initial check (before sub_version recognition) - switch (version) { - case GameVersion::DC: - return Client::Flag::NO_D6 | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case GameVersion::GC: - return 0; - case GameVersion::XB: - return Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case GameVersion::PC: - return Client::Flag::NO_D6 | - Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case GameVersion::PATCH: - return Client::Flag::NO_D6 | - Client::Flag::NO_SEND_FUNCTION_CALL; - case GameVersion::BB: - return Client::Flag::NO_D6 | - Client::Flag::SAVE_ENABLED | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - } - break; - - case 0x20: // DCNTE, possibly also DCv1 JP - case 0x21: // DCv1 US - // In the case of DCNTE, the IS_DC_TRIAL_EDITION flag is already set when - // we get here, so the remaining flags are the same as DCv1 - return Client::Flag::IS_DC_V1 | - Client::Flag::NO_D6 | - Client::Flag::NO_SEND_FUNCTION_CALL; - case 0x23: // DCv1 EU - return Client::Flag::IS_DC_V1 | - Client::Flag::NO_D6 | - Client::Flag::NO_SEND_FUNCTION_CALL; - - case 0x25: // DCv2 JP - case 0x26: // DCv2 US - case 0x28: // DCv2 EU - return Client::Flag::NO_D6 | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - - case 0x29: // PC - return Client::Flag::NO_D6 | - Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - - case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 JP v1.02, at least one version of PSO XB - case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00 - case 0x34: // GC Ep1&2 JP v1.03 - // In the case of GC Trial Edition, the IS_GC_TRIAL_EDITION flag is - // already set when we get here (because the client has used V2 encryption - // instead of V3) - return 0; - case 0x32: // GC Ep1&2 EU 50Hz - case 0x33: // GC Ep1&2 EU 60Hz - return Client::Flag::NO_D6_AFTER_LOBBY; - case 0x35: // GC Ep1&2 JP v1.04 (Plus) - return Client::Flag::NO_D6_AFTER_LOBBY | - Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case 0x36: // GC Ep1&2 US v1.02 (Plus) - case 0x39: // GC Ep1&2 JP v1.05 (Plus) - return Client::Flag::NO_D6_AFTER_LOBBY | - Client::Flag::NO_SEND_FUNCTION_CALL; - - case 0x40: // GC Ep3 JP and Trial Edition - // sub_version can't be used to tell JP final and Trial Edition apart; we - // instead look at header.flag in the 61 command. - return Client::Flag::NO_D6_AFTER_LOBBY | - Client::Flag::IS_EPISODE_3 | - Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case 0x42: // Also GC Ep3 JP? - return Client::Flag::NO_D6_AFTER_LOBBY | - Client::Flag::IS_EPISODE_3 | - Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case 0x41: // GC Ep3 US - return Client::Flag::NO_D6_AFTER_LOBBY | - Client::Flag::IS_EPISODE_3 | - Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL | - Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - case 0x43: // GC Ep3 EU - return Client::Flag::NO_D6_AFTER_LOBBY | - Client::Flag::IS_EPISODE_3 | - Client::Flag::NO_SEND_FUNCTION_CALL; - } - throw runtime_error(string_printf("unknown sub_version %" PRIX64, sub_version)); -} - const char* name_for_version(GameVersion version) { switch (version) { case GameVersion::GC: diff --git a/src/Version.hh b/src/Version.hh index 58efca71..93e3aac6 100644 --- a/src/Version.hh +++ b/src/Version.hh @@ -28,7 +28,6 @@ extern const std::vector version_to_login_port_name; extern const std::vector version_to_lobby_port_name; extern const std::vector version_to_proxy_port_name; -uint32_t flags_for_version(GameVersion version, int64_t sub_version); const char* name_for_version(GameVersion version); GameVersion version_for_name(const char* name); diff --git a/tests/DCv1-GameSmokeTest.test.txt b/tests/DCv1-GameSmokeTest.test.txt index 7bca47ee..446bf6cc 100644 --- a/tests/DCv1-GameSmokeTest.test.txt +++ b/tests/DCv1-GameSmokeTest.test.txt @@ -58,9 +58,7 @@ I 40469 2023-05-26 10:40:55 - [Commands] Received from C-1 (version=DC command=9 0110 | 00 00 00 00 | I 40469 2023-05-26 10:40:55 - [C-1] Game version changed to DC I 40469 2023-05-26 10:40:55 - [Commands] Sending to C-1 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 14 02 0A 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww I 40469 2023-05-26 10:40:55 - [Commands] Sending to C-1 (version=DC command=07 flag=05) 0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria @@ -82,10 +80,6 @@ I 40469 2023-05-26 10:40:58 - [Commands] Received from C-1 (version=DC command=1 0000 | 10 00 0C 00 11 00 00 11 11 22 22 11 | "" I 40469 2023-05-26 10:40:58 - [Commands] Sending to C-1 (version=DC command=97 flag=01) 0000 | 97 01 04 00 | -I 40469 2023-05-26 10:40:58 - [Commands] Sending to C-1 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 14 06 0A 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | I 40469 2023-05-26 10:40:58 - [Commands] Received from C-1 (version=DC command=B1 flag=00) 0000 | B1 00 04 00 | I 40469 2023-05-26 10:40:58 - [Commands] Sending to C-1 (version=DC command=B1 flag=00) @@ -130,9 +124,7 @@ I 40469 2023-05-26 10:40:59 - [Commands] Received from C-2 (version=GC command=9 00A0 | 6C 69 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | li I 40469 2023-05-26 10:40:59 - [C-2] Game version changed to DC I 40469 2023-05-26 10:40:59 - [Commands] Sending to C-2 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 14 02 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww I 40469 2023-05-26 10:40:59 - [Commands] Sending to C-2 (version=DC command=04 flag=00) 0000 | 90 01 04 00 | I 40469 2023-05-26 10:40:59 - [Commands] Received from C-2 (version=DC command=61 flag=01) @@ -161,10 +153,6 @@ I 40469 2023-05-26 10:40:59 - [Commands] Received from C-2 (version=GC command=9 0080 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 54 61 | Ta 00A0 | 6C 69 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | li -I 40469 2023-05-26 10:40:59 - [Commands] Sending to C-2 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 14 02 08 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | I 40469 2023-05-26 10:40:59 - [Commands] Sending to C-2 (version=DC command=83 flag=0A) 0000 | 83 0A 7C 00 33 00 00 33 01 00 00 00 00 00 00 00 | | 3 3 0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3 diff --git a/tests/DCv2-GameSmokeTest.test.txt b/tests/DCv2-GameSmokeTest.test.txt index 50e3d12f..ba5da646 100644 --- a/tests/DCv2-GameSmokeTest.test.txt +++ b/tests/DCv2-GameSmokeTest.test.txt @@ -57,9 +57,7 @@ I 40992 2023-05-26 10:52:51 - [Commands] Received from C-1 (version=DC command=9 0120 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 40992 2023-05-26 10:52:51 - [C-1] Game version changed to DC I 40992 2023-05-26 10:52:51 - [Commands] Sending to C-1 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 04 00 02 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww I 40992 2023-05-26 10:52:51 - [Commands] Sending to C-1 (version=DC command=07 flag=05) 0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria @@ -81,10 +79,6 @@ I 40992 2023-05-26 10:52:56 - [Commands] Received from C-1 (version=DC command=1 0000 | 10 00 0C 00 11 00 00 11 11 22 22 11 | "" I 40992 2023-05-26 10:52:56 - [Commands] Sending to C-1 (version=DC command=97 flag=01) 0000 | 97 01 04 00 | -I 40992 2023-05-26 10:52:56 - [Commands] Sending to C-1 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 04 04 02 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | I 40992 2023-05-26 10:52:57 - [Commands] Received from C-1 (version=DC command=B1 flag=00) 0000 | B1 00 04 00 | I 40992 2023-05-26 10:52:57 - [Commands] Sending to C-1 (version=DC command=B1 flag=00) @@ -131,9 +125,7 @@ I 40992 2023-05-26 10:52:57 - [Commands] Received from C-2 (version=GC command=9 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 40992 2023-05-26 10:52:57 - [C-2] Game version changed to DC I 40992 2023-05-26 10:52:57 - [Commands] Sending to C-2 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 04 00 02 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww I 40992 2023-05-26 10:52:57 - [Commands] Sending to C-2 (version=DC command=83 flag=0F) 0000 | 83 0F B8 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3 0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3 @@ -1603,9 +1595,7 @@ I 40992 2023-05-26 10:55:37 - [Commands] Received from C-3 (version=DC command=9 0120 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 40992 2023-05-26 10:55:37 - [C-3] Game version changed to DC I 40992 2023-05-26 10:55:37 - [Commands] Sending to C-3 (version=DC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 77 77 77 77 39 98 AC 82 | , wwww9 -0010 | 0E 89 2A 49 04 00 02 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww I 40992 2023-05-26 10:55:37 - [Commands] Sending to C-3 (version=DC command=07 flag=05) 0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria diff --git a/tests/GC-Episode3Battle.test.txt b/tests/GC-Episode3Battle.test.txt index 7527e23f..21d0eef9 100644 --- a/tests/GC-Episode3Battle.test.txt +++ b/tests/GC-Episode3Battle.test.txt @@ -57,9 +57,9 @@ I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (version=GC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 03 02 00 30 45 53 33 00 00 00 00 | *I 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=B7 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF | @@ -2618,17 +2618,17 @@ I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=CC f 04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0500 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 43 02 00 30 45 53 33 00 00 00 00 | *I C 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00) 0000 | 99 00 04 00 | I 16332 2023-09-17 10:14:34 - [Commands] Received from C-1 (Tali) (version=GC command=D6 flag=00) 0000 | D6 00 04 00 | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 42 02 00 30 45 53 33 00 00 00 00 | *I B 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria @@ -2650,9 +2650,9 @@ I 16332 2023-09-17 10:14:35 - [Commands] Received from C-1 (Tali) (version=GC co I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC command=97 flag=01) 0000 | 97 01 04 00 | I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:35 - [Commands] Received from C-1 (Tali) (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | I 16332 2023-09-17 10:14:35 - [Commands] Sending to C-1 (Tali) (version=GC command=B1 flag=00) @@ -2701,13 +2701,13 @@ I 16332 2023-09-17 10:14:37 - [Commands] Received from C-2 (version=GC command=9 0090 | 31 31 31 31 31 31 31 31 00 00 00 00 00 00 00 00 | 11111111 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali -00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 39 98 AC 82 | 9 -00D0 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -00E0 | 00 00 FF FF FF FF FF FF FF FF FF FF | +00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 +00D0 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=83 flag=14) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF | @@ -3583,9 +3583,9 @@ I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (Tali) (version=GC comma 0430 | 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF | 0440 | FF FF FF FF FF FF FF FF FF FF FF FF | I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0E 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 16332 2023-09-17 10:14:39 - [Commands] Received from C-2 (Tali) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ? 0010 | B2 84 1C BF 00 00 00 00 53 88 BA C2 | S diff --git a/tests/GC-Episode3BattleWithSpectator.test.txt b/tests/GC-Episode3BattleWithSpectator.test.txt index 6310895e..edf58a32 100644 --- a/tests/GC-Episode3BattleWithSpectator.test.txt +++ b/tests/GC-Episode3BattleWithSpectator.test.txt @@ -57,9 +57,9 @@ I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (version=GC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 03 02 00 30 45 53 33 00 00 00 00 | *I 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 42 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=B7 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF | @@ -2618,17 +2618,17 @@ I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=CC f 04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0500 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 43 02 00 30 45 53 33 00 00 00 00 | *I C 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 4A 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC command=99 flag=00) 0000 | 99 00 04 00 | I 17097 2023-09-19 21:52:47 - [Commands] Received from C-1 (Tali) (version=GC command=D6 flag=00) 0000 | D6 00 04 00 | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 42 02 00 30 45 53 33 00 00 00 00 | *I B 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 48 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (Tali) (version=GC command=07 flag=06) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria @@ -2650,9 +2650,9 @@ I 17097 2023-09-19 21:52:48 - [Commands] Received from C-1 (Tali) (version=GC co I 17097 2023-09-19 21:52:48 - [Commands] Sending to C-1 (Tali) (version=GC command=97 flag=01) 0000 | 97 01 04 00 | I 17097 2023-09-19 21:52:48 - [Commands] Sending to C-1 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:49 - [Commands] Received from C-1 (Tali) (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | I 17097 2023-09-19 21:52:49 - [Commands] Sending to C-1 (Tali) (version=GC command=B1 flag=00) @@ -2701,13 +2701,13 @@ I 17097 2023-09-19 21:52:50 - [Commands] Received from C-2 (version=GC command=9 0090 | 31 31 31 31 31 31 31 31 00 00 00 00 00 00 00 00 | 11111111 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali -00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 39 98 AC 82 | 9 -00D0 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -00E0 | 00 00 FF FF FF FF FF FF FF FF FF FF | +00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 +00D0 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 91 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF | @@ -3583,9 +3583,9 @@ I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (Tali) (version=GC comma 0430 | 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF | 0440 | FF FF FF FF FF FF FF FF FF FF FF FF | I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0E 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 30 45 53 33 40 93 00 4C 60 00 00 00 00 00 00 00 | 0ES3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:52:52 - [Commands] Received from C-2 (Tali) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ? 0010 | B2 84 1C BF 00 00 00 00 53 88 BA C2 | S @@ -5864,9 +5864,9 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (version=GC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 09 02 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 +0010 | 00 00 00 33 40 A1 00 42 60 00 00 00 00 00 00 00 | 3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=B7 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF | @@ -8425,17 +8425,17 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=CC f 04F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0500 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 49 02 00 00 00 00 33 00 00 00 00 | *I I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 +0010 | 00 00 00 33 40 A1 00 4A 60 00 00 00 00 00 00 00 | 3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=99 flag=00) 0000 | 99 00 04 00 | I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=D6 flag=00) 0000 | D6 00 04 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 48 02 00 00 00 00 33 00 00 00 00 | *I H 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 +0010 | 00 00 00 33 40 A1 00 48 60 00 00 00 00 00 00 00 | 3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=07 flag=08) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria @@ -8457,9 +8457,9 @@ I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC co I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=97 flag=01) 0000 | 97 01 04 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 4C 02 00 00 00 00 33 00 00 00 00 | *I L 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 +0010 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:53 - [Commands] Received from C-3 (Tali) (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (Tali) (version=GC command=B1 flag=00) @@ -8508,13 +8508,13 @@ I 17097 2023-09-19 21:53:54 - [Commands] Received from C-4 (version=GC command=9 0090 | 32 32 32 32 32 32 32 32 00 00 00 00 00 00 00 00 | 22222222 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali -00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 39 98 AC 82 | 9 -00D0 | 0E 89 2A 49 0A 4C 02 00 00 00 00 33 00 00 00 00 | *I L 3 -00E0 | 00 00 FF FF FF FF FF FF FF FF FF FF | +00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , """"2 +00D0 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0A 4C 02 00 00 00 00 33 00 00 00 00 | *I L 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 +0010 | 00 00 00 33 40 A1 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00) 0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF | @@ -9390,9 +9390,9 @@ I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (Tali) (version=GC comma 0430 | 00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF | 0440 | FF FF FF FF FF FF FF FF FF FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (Tali) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 0E 4C 02 00 00 00 00 33 00 00 00 00 | *I L 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 32 AC 99 83 | , """"2 +0010 | 00 00 00 33 40 A3 00 4C 60 00 00 00 00 00 00 00 | 3@ ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 17097 2023-09-19 21:53:54 - [Commands] Received from C-4 (Tali) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 00 0F 00 FF FF | ` ? 0010 | B2 84 1C BF 00 00 00 00 53 88 BA C2 | S diff --git a/tests/GC-ForestGame.test.txt b/tests/GC-ForestGame.test.txt index ed760807..2b4cc93a 100644 --- a/tests/GC-ForestGame.test.txt +++ b/tests/GC-ForestGame.test.txt @@ -57,9 +57,9 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 02 01 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 00 00 00 33 00 01 00 42 60 00 00 00 00 00 00 00 | 3 ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=D5 flag=00) 0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn 0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex @@ -74,9 +74,9 @@ I 49108 2023-05-26 16:18:01 - [Commands] Received from C-1 (version=GC command=9 I 49108 2023-05-26 16:18:02 - [Commands] Received from C-1 (version=GC command=D6 flag=00) 0000 | D6 00 04 00 | I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 02 00 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 00 00 00 33 00 01 00 40 60 00 00 00 00 00 00 00 | 3 ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:02 - [Commands] Sending to C-1 (version=GC command=07 flag=08) 0000 | 07 06 C8 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria @@ -96,9 +96,9 @@ I 49108 2023-05-26 16:18:06 - [Commands] Received from C-1 (version=GC command=1 I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=97 flag=01) 0000 | 97 01 04 00 | I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 02 04 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:06 - [Commands] Received from C-1 (version=GC command=B1 flag=00) 0000 | B1 00 04 00 | I 49108 2023-05-26 16:18:06 - [Commands] Sending to C-1 (version=GC command=B1 flag=00) @@ -147,13 +147,13 @@ I 49108 2023-05-26 16:18:08 - [Commands] Received from C-2 (version=GC command=9 0090 | 31 31 31 31 31 31 31 31 00 00 00 00 00 00 00 00 | 11111111 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 4A 65 73 73 | Jess -00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 39 98 AC 82 | 9 -00D0 | 0E 89 2A 49 02 04 00 00 00 00 00 33 00 00 00 00 | *I 3 -00E0 | 00 00 FF FF FF FF FF FF FF FF FF FF | +00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 +00D0 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` +00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 02 04 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 00 00 00 33 00 01 00 44 60 00 00 00 00 00 00 00 | 3 ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (version=GC command=83 flag=0F) 0000 | 83 0F B8 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3 0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3 @@ -364,9 +364,9 @@ I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (Jess) (version=GC comma 0430 | 00 05 00 00 00 00 01 00 05 0E 00 04 03 00 0E 02 | 0440 | 0D FF 05 03 01 01 00 04 00 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Sending to C-2 (Jess) (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 06 04 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:18:08 - [Commands] Received from C-2 (Jess) (version=GC command=60 flag=00) 0000 | 60 00 1C 00 3F 06 00 00 00 00 00 80 0F 00 FF FF | ` ? 0010 | 00 00 00 00 00 00 A0 41 00 00 07 43 | A C @@ -10117,9 +10117,9 @@ I 49108 2023-05-26 16:28:29 - [Commands] Received from C-3 (version=GC command=9 0090 | 31 31 31 31 31 31 31 31 00 00 00 00 00 00 00 00 | 11111111 00A0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 4A 65 73 73 | Jess -00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 39 98 AC 82 | 9 -00D0 | 0E 89 2A 49 06 04 00 00 00 00 00 33 00 00 00 00 | *I 3 -00E0 | 00 00 FF FF FF FF FF FF FF FF FF FF 00 00 00 00 | +00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 32 AC 99 83 | , 2 +00D0 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` +00E0 | 00 00 00 00 00 00 FF FF 80 FF FF FF 00 00 00 00 | 00F0 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0110 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | @@ -10127,9 +10127,9 @@ I 49108 2023-05-26 16:28:29 - [Commands] Received from C-3 (version=GC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=04 flag=00) -0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 06 04 00 00 00 00 00 33 00 00 00 00 | *I 3 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 32 AC 99 83 | , 2 +0010 | 00 00 00 33 00 03 00 44 60 00 00 00 00 00 00 00 | 3 ` +0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF | I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=07 flag=06) 0000 | 07 05 AC 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al 0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria diff --git a/tests/PC-BasicGame.test.txt b/tests/PC-BasicGame.test.txt index 8ef817ab..77ffbe41 100644 --- a/tests/PC-BasicGame.test.txt +++ b/tests/PC-BasicGame.test.txt @@ -57,9 +57,7 @@ I 49484 2023-05-26 16:35:10 - [Commands] Received from C-2 (version=PC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 49484 2023-05-26 16:35:10 - [Commands] Sending to C-2 (version=PC command=04 flag=00) -0000 | 2C 00 04 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 04 10 02 00 00 00 00 32 00 00 00 00 | *I 2 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 0C 00 04 00 00 00 01 00 11 11 11 11 | I 49484 2023-05-26 16:35:10 - [Commands] Sending to C-2 (version=PC command=07 flag=05) 0000 | 0C 01 07 05 11 00 00 11 FF FF FF FF 04 00 41 00 | A 0010 | 6C 00 65 00 78 00 61 00 6E 00 64 00 72 00 69 00 | l e x a n d r i @@ -89,10 +87,6 @@ I 49484 2023-05-26 16:35:14 - [Commands] Received from C-2 (version=PC command=1 0000 | 0C 00 10 00 11 00 00 11 11 22 22 11 | "" I 49484 2023-05-26 16:35:14 - [Commands] Sending to C-2 (version=PC command=97 flag=01) 0000 | 04 00 97 01 | -I 49484 2023-05-26 16:35:14 - [Commands] Sending to C-2 (version=PC command=04 flag=00) -0000 | 2C 00 04 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 04 14 02 00 00 00 00 32 00 00 00 00 | *I 2 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | I 49484 2023-05-26 16:35:14 - [Commands] Received from C-2 (version=PC command=B1 flag=00) 0000 | 04 00 B1 00 | I 49484 2023-05-26 16:35:14 - [Commands] Sending to C-2 (version=PC command=B1 flag=00) @@ -138,9 +132,7 @@ I 49484 2023-05-26 16:35:14 - [Commands] Received from C-3 (version=PC command=9 00B0 | 00 00 00 00 00 00 00 00 00 00 00 00 54 61 6C 69 | Tali 00C0 | 00 00 00 00 00 00 00 00 00 00 00 00 | I 49484 2023-05-26 16:35:14 - [Commands] Sending to C-3 (version=PC command=04 flag=00) -0000 | 2C 00 04 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 04 10 02 00 00 00 00 32 00 00 00 00 | *I 2 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 0C 00 04 00 00 00 01 00 11 11 11 11 | I 49484 2023-05-26 16:35:14 - [Commands] Sending to C-3 (version=PC command=83 flag=0F) 0000 | B8 00 83 0F 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3 0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3 @@ -1168,9 +1160,7 @@ I 49484 2023-05-26 16:37:03 - [Commands] Received from C-4 (version=PC command=9 0130 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 0140 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | I 49484 2023-05-26 16:37:03 - [Commands] Sending to C-4 (version=PC command=04 flag=00) -0000 | 2C 00 04 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9 -0010 | 0E 89 2A 49 04 10 02 00 00 00 00 32 00 00 00 00 | *I 2 -0020 | 00 00 FF FF FF FF FF FF FF FF FF FF | +0000 | 0C 00 04 00 00 00 01 00 11 11 11 11 | I 49484 2023-05-26 16:37:03 - [Commands] Sending to C-4 (version=PC command=07 flag=05) 0000 | 0C 01 07 05 11 00 00 11 FF FF FF FF 04 00 41 00 | A 0010 | 6C 00 65 00 78 00 61 00 6E 00 64 00 72 00 69 00 | l e x a n d r i