diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 37245970..c03c83ab 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -88,10 +88,10 @@ static void check_is_leader(shared_ptr l, shared_ptr c) { //////////////////////////////////////////////////////////////////////////////// // Message commands -static void server_command_lobby_info(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_lobby_info(shared_ptr c, const std::u16string&) { vector lines; + auto l = c->lobby.lock(); if (!l) { lines.emplace_back("$C4No lobby info"); @@ -141,22 +141,20 @@ static void server_command_lobby_info(shared_ptr, shared_ptr send_text_message(c, decode_sjis(join(lines, "\n"))); } -static void proxy_command_lobby_info(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_lobby_info(shared_ptr ses, const std::u16string&) { string msg; // On non-masked-GC sessions (BB), there is no remote Guild Card number, so we // don't show it. (The user can see it in the pause menu, unlike in masked-GC // sessions like GC.) - if (session.remote_guild_card_number >= 0) { - msg = string_printf("$C7GC: $C6%" PRId64 "$C7\n", - session.remote_guild_card_number); + if (ses->remote_guild_card_number >= 0) { + msg = string_printf("$C7GC: $C6%" PRId64 "$C7\n", ses->remote_guild_card_number); } msg += "Slots: "; - for (size_t z = 0; z < session.lobby_players.size(); z++) { - bool is_self = z == session.lobby_client_id; - bool is_leader = z == session.leader_client_id; - if (session.lobby_players[z].guild_card_number == 0) { + for (size_t z = 0; z < ses->lobby_players.size(); z++) { + bool is_self = z == ses->lobby_client_id; + bool is_leader = z == ses->leader_client_id; + if (ses->lobby_players[z].guild_card_number == 0) { msg += string_printf("$C0%zX$C7", z); } else if (is_self && is_leader) { msg += string_printf("$C6%zX$C7", z); @@ -170,13 +168,13 @@ static void proxy_command_lobby_info(shared_ptr, } vector cheats_tokens; - if (session.options.switch_assist) { + if (ses->options.switch_assist) { cheats_tokens.emplace_back("SWA"); } - if (session.options.infinite_hp) { + if (ses->options.infinite_hp) { cheats_tokens.emplace_back("HP"); } - if (session.options.infinite_tp) { + if (ses->options.infinite_tp) { cheats_tokens.emplace_back("TP"); } if (!cheats_tokens.empty()) { @@ -185,13 +183,13 @@ static void proxy_command_lobby_info(shared_ptr, } vector behaviors_tokens; - if (session.options.save_files) { + if (ses->options.save_files) { behaviors_tokens.emplace_back("SAVE"); } - if (session.options.suppress_remote_login) { + if (ses->options.suppress_remote_login) { behaviors_tokens.emplace_back("SL"); } - if (session.options.function_call_return_value >= 0) { + if (ses->options.function_call_return_value >= 0) { behaviors_tokens.emplace_back("BFC"); } if (!behaviors_tokens.empty()) { @@ -199,52 +197,48 @@ static void proxy_command_lobby_info(shared_ptr, msg += join(behaviors_tokens, ","); } - if (session.options.override_section_id >= 0) { + if (ses->options.override_section_id >= 0) { msg += "\n$C7SecID*: $C6"; - msg += name_for_section_id(session.options.override_section_id); + msg += name_for_section_id(ses->options.override_section_id); } - send_text_message(session.client_channel, decode_sjis(msg)); + send_text_message(ses->client_channel, decode_sjis(msg)); } -static void server_command_ax(shared_ptr, shared_ptr, - shared_ptr c, const std::u16string& args) { +static void server_command_ax(shared_ptr c, const std::u16string& args) { check_privileges(c, Privilege::ANNOUNCE); string message = encode_sjis(args); ax_messages_log.info("%s", message.c_str()); } -static void server_command_announce(shared_ptr s, shared_ptr, - shared_ptr c, const std::u16string& args) { +static void server_command_announce(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); check_privileges(c, Privilege::ANNOUNCE); send_text_message(s, args); } -static void server_command_arrow(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { - // no preconditions +static void server_command_arrow(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); c->lobby_arrow_color = stoull(encode_sjis(args), nullptr, 0); if (!l->is_game()) { send_arrow_update(l); } } -static void proxy_command_arrow(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { - session.server_channel.send(0x89, stoull(encode_sjis(args), nullptr, 0)); +static void proxy_command_arrow(shared_ptr ses, const std::u16string& args) { + ses->server_channel.send(0x89, stoull(encode_sjis(args), nullptr, 0)); } -static void server_command_debug(shared_ptr, shared_ptr, - shared_ptr c, const std::u16string&) { +static void server_command_debug(shared_ptr c, const std::u16string&) { check_privileges(c, Privilege::DEBUG); c->options.debug = !c->options.debug; send_text_message_printf(c, "Debug %s", c->options.debug ? "enabled" : "disabled"); } -static void server_command_auction(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_auction(shared_ptr c, const std::u16string&) { check_privileges(c, Privilege::DEBUG); + auto l = c->require_lobby(); if (l->is_game() && l->is_ep3()) { G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; cmd.header.sender_client_id = c->lobby_client_id; @@ -252,23 +246,22 @@ static void server_command_auction(shared_ptr, shared_ptr l, } } -static void proxy_command_auction(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_auction(shared_ptr ses, const std::u16string&) { G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; - cmd.header.sender_client_id = session.lobby_client_id; - session.client_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); - session.server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); + cmd.header.sender_client_id = ses->lobby_client_id; + ses->client_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); + ses->server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); } -static void server_command_patch(shared_ptr s, shared_ptr, - shared_ptr c, const std::u16string& args) { +static void server_command_patch(shared_ptr c, const std::u16string& args) { string basename = encode_sjis(args); try { - prepare_client_for_patches(s, c, [s, wc = weak_ptr(c), basename]() { + prepare_client_for_patches(c, [wc = weak_ptr(c), basename]() { auto c = wc.lock(); if (!c) { return; } + auto s = c->require_server_state(); // 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( @@ -283,49 +276,51 @@ static void server_command_patch(shared_ptr s, shared_ptr, static void empty_patch_return_handler(uint32_t, uint32_t) {} -static void proxy_command_patch(shared_ptr s, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_patch(shared_ptr ses, const std::u16string& args) { string basename = encode_sjis(args); - auto send_call = [s, basename, &session](uint32_t specific_version, uint32_t) { + auto send_call = [basename, ses](uint32_t specific_version, uint32_t) { try { - if (session.newserv_client_config.cfg.specific_version != specific_version) { - session.newserv_client_config.cfg.specific_version = specific_version; - session.log.info("Version detected as %08" PRIX32, session.newserv_client_config.cfg.specific_version); + 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); } + 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, basename.c_str(), session.newserv_client_config.cfg.specific_version)); + string_printf("%s-%08" PRIX32, basename.c_str(), ses->newserv_client_config.cfg.specific_version)); send_function_call( - session.client_channel, session.newserv_client_config.cfg.flags, fn); + ses->client_channel, ses->newserv_client_config.cfg.flags, fn); // Don't forward the patch response to the server - session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler); + ses->function_call_return_handler_queue.emplace_back(empty_patch_return_handler); } catch (const out_of_range&) { - send_text_message(session.client_channel, u"Invalid patch name"); + send_text_message(ses->client_channel, u"Invalid patch name"); } }; - auto send_version_detect_or_send_call = [s, basename, &session, send_call]() { - if (session.version == GameVersion::GC && - session.newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + auto send_version_detect_or_send_call = [basename, ses, send_call]() { + if (ses->version == GameVersion::GC && + ses->newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + auto s = ses->require_server_state(); send_function_call( - session.client_channel, - session.newserv_client_config.cfg.flags, + ses->client_channel, + ses->newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("VersionDetect")); - session.function_call_return_handler_queue.emplace_back(send_call); + ses->function_call_return_handler_queue.emplace_back(send_call); } else { - send_call(session.newserv_client_config.cfg.specific_version, 0); + send_call(ses->newserv_client_config.cfg.specific_version, 0); } }; // This mirrors the implementation in prepare_client_for_patches - if (!(session.newserv_client_config.cfg.flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { + if (!(ses->newserv_client_config.cfg.flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { + auto s = ses->require_server_state(); send_function_call( - session.client_channel, session.newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2734EC); - session.function_call_return_handler_queue.emplace_back([s, session_p = &session, send_version_detect_or_send_call](uint32_t, uint32_t) -> void { + ses->client_channel, ses->newserv_client_config.cfg.flags, 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( - session_p->client_channel, session_p->newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); - session_p->function_call_return_handler_queue.emplace_back([session_p, send_version_detect_or_send_call](uint32_t, uint32_t) -> void { - session_p->newserv_client_config.cfg.flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; + ses->client_channel, ses->newserv_client_config.cfg.flags, 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; send_version_detect_or_send_call(); }); }); @@ -334,9 +329,9 @@ static void proxy_command_patch(shared_ptr s, } } -static void server_command_persist(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_persist(shared_ptr c, const std::u16string&) { check_privileges(c, Privilege::DEBUG); + auto l = c->require_lobby(); if (l->flags & Lobby::Flag::DEFAULT) { send_text_message(c, u"$C6Default lobbies\ncannot be marked\ntemporary"); } else { @@ -346,8 +341,8 @@ static void server_command_persist(shared_ptr, shared_ptr l, } } -static void server_command_exit(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_exit(shared_ptr c, const std::u16string&) { + auto l = c->require_lobby(); if (l->is_game()) { if (c->flags & Client::Flag::IS_EPISODE_3) { c->channel.send(0xED, 0x00); @@ -366,41 +361,39 @@ static void server_command_exit(shared_ptr s, shared_ptr l, const auto& port_name = version_to_login_port_name.at( static_cast(c->version())); + auto s = c->require_server_state(); send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port); } } -static void proxy_command_exit(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string&) { - if (session.is_in_game) { - if (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { - session.client_channel.send(0xED, 0x00); - } else if (session.is_in_quest) { +static void proxy_command_exit(shared_ptr ses, const std::u16string&) { + if (ses->is_in_game) { + if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { + ses->client_channel.send(0xED, 0x00); + } else if (ses->is_in_quest) { G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; - session.client_channel.send(0x60, 0x00, cmd); + ses->client_channel.send(0x60, 0x00, cmd); } else { - send_text_message(session.client_channel, u"$C6You must return to\nthe lobby first"); + send_text_message(ses->client_channel, u"$C6You must return to\nthe lobby first"); } } else { - session.disconnect_action = ProxyServer::LinkedSession::DisconnectAction::CLOSE_IMMEDIATELY; - session.send_to_game_server(); + ses->disconnect_action = ProxyServer::LinkedSession::DisconnectAction::CLOSE_IMMEDIATELY; + ses->send_to_game_server(); } } -static void server_command_get_self_card(shared_ptr, shared_ptr, - shared_ptr c, const std::u16string&) { +static void server_command_get_self_card(shared_ptr c, const std::u16string&) { send_guild_card(c, c); } -static void proxy_command_get_player_card(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& u16args) { +static void proxy_command_get_player_card(shared_ptr ses, const std::u16string& u16args) { string args = encode_sjis(u16args); bool any_card_sent = false; - for (const auto& p : session.lobby_players) { + for (const auto& p : ses->lobby_players) { if (!p.name.empty() && args == p.name) { - send_guild_card(session.client_channel, p.guild_card_number, decode_sjis(p.name), u"", u"", p.section_id, p.char_class); + send_guild_card(ses->client_channel, p.guild_card_number, decode_sjis(p.name), u"", u"", p.section_id, p.char_class); any_card_sent = true; } } @@ -408,42 +401,40 @@ static void proxy_command_get_player_card(shared_ptr, if (!any_card_sent) { try { size_t index = stoull(args, nullptr, 0); - const auto& p = session.lobby_players.at(index); + const auto& p = ses->lobby_players.at(index); if (!p.name.empty()) { - send_guild_card(session.client_channel, p.guild_card_number, decode_sjis(p.name), u"", u"", p.section_id, p.char_class); + send_guild_card(ses->client_channel, p.guild_card_number, decode_sjis(p.name), u"", u"", p.section_id, p.char_class); } } catch (const exception& e) { - send_text_message_printf(session.client_channel, "Error: %s", e.what()); + send_text_message_printf(ses->client_channel, "Error: %s", e.what()); } } } -static void server_command_send_client(shared_ptr, shared_ptr, - shared_ptr c, const std::u16string& args) { +static void server_command_send_client(shared_ptr c, const std::u16string& args) { string data = parse_data_string(encode_sjis(args)); data.resize((data.size() + 3) & (~3)); c->channel.send(data); } -static void proxy_command_send_client(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_send_client(shared_ptr ses, const std::u16string& args) { string data = parse_data_string(encode_sjis(args)); data.resize((data.size() + 3) & (~3)); - session.client_channel.send(data); + ses->client_channel.send(data); } -static void proxy_command_send_server(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_send_server(shared_ptr ses, const std::u16string& args) { string data = parse_data_string(encode_sjis(args)); data.resize((data.size() + 3) & (~3)); - session.server_channel.send(data); + ses->server_channel.send(data); } //////////////////////////////////////////////////////////////////////////////// // Lobby commands -static void server_command_cheat(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_cheat(shared_ptr c, const std::u16string&) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); @@ -470,8 +461,8 @@ static void server_command_cheat(shared_ptr s, shared_ptr l, } } -static void server_command_lobby_event(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_lobby_event(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, false); check_privileges(c, Privilege::CHANGE_EVENT); @@ -485,28 +476,26 @@ static void server_command_lobby_event(shared_ptr, shared_ptrevent); } -static void proxy_command_lobby_event(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_lobby_event(shared_ptr ses, const std::u16string& args) { if (args.empty()) { - session.options.override_lobby_event = -1; + ses->options.override_lobby_event = -1; } else { uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { - send_text_message(session.client_channel, u"$C6No such lobby event."); + send_text_message(ses->client_channel, u"$C6No such lobby event."); } else { - session.options.override_lobby_event = new_event; + ses->options.override_lobby_event = new_event; // This command is supported on all V3 versions except Ep1&2 Trial - if ((session.version == GameVersion::GC && !(session.newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || - (session.version == GameVersion::XB) || - (session.version == GameVersion::BB)) { - session.client_channel.send(0xDA, session.options.override_lobby_event); + if ((ses->version == GameVersion::GC && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || + (ses->version == GameVersion::XB) || + (ses->version == GameVersion::BB)) { + ses->client_channel.send(0xDA, ses->options.override_lobby_event); } } } } -static void server_command_lobby_event_all(shared_ptr s, shared_ptr, - shared_ptr c, const std::u16string& args) { +static void server_command_lobby_event_all(shared_ptr c, const std::u16string& args) { check_privileges(c, Privilege::CHANGE_EVENT); uint8_t new_event = event_for_name(args); @@ -515,6 +504,7 @@ static void server_command_lobby_event_all(shared_ptr s, shared_ptr return; } + auto s = c->require_server_state(); for (auto l : s->all_lobbies()) { if (l->is_game() || !(l->flags & Lobby::Flag::DEFAULT)) { continue; @@ -525,8 +515,8 @@ static void server_command_lobby_event_all(shared_ptr s, shared_ptr } } -static void server_command_lobby_type(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_lobby_type(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, false); uint8_t new_type = args.empty() ? 0 : lobby_type_for_name(args); @@ -540,20 +530,19 @@ static void server_command_lobby_type(shared_ptr, shared_ptr send_join_lobby(c, l); } -static void proxy_command_lobby_type(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_lobby_type(shared_ptr ses, const std::u16string& args) { uint8_t new_type = args.empty() ? 0 : lobby_type_for_name(args); if (new_type == 0x80) { - send_text_message(session.client_channel, u"$C6No such lobby type"); + send_text_message(ses->client_channel, u"$C6No such lobby type"); return; } - uint8_t max_standard_type = ((session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) ? 20 : 15); - session.options.override_lobby_number = (new_type < max_standard_type) ? -1 : new_type; + 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; } -static void server_command_saverec(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_saverec(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); if (args.find(u'/') != string::npos) { send_text_message(c, u"$C4Recording names\ncannot include\nthe / character"); return; @@ -569,8 +558,7 @@ static void server_command_saverec(shared_ptr, shared_ptr l, l->prev_battle_record.reset(); } -static void server_command_playrec(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_playrec(shared_ptr c, const std::u16string& args) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { send_text_message(c, u"$C4This command can\nonly be used on\nEpisode 3"); return; @@ -580,9 +568,11 @@ static void server_command_playrec(shared_ptr s, shared_ptr return; } + auto l = c->require_lobby(); if (l->battle_player) { l->battle_player->start(); } else { + auto s = c->require_server_state(); uint32_t flags = Lobby::Flag::NON_V1_ONLY | Lobby::Flag::IS_SPECTATOR_TEAM; string filename = encode_sjis(args); if (filename[0] == '!') { @@ -605,8 +595,8 @@ static void server_command_playrec(shared_ptr s, shared_ptr //////////////////////////////////////////////////////////////////////////////// // Game commands -static void server_command_secid(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_secid(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, false); if (!args[0]) { @@ -623,24 +613,23 @@ static void server_command_secid(shared_ptr, shared_ptr l, } } -static void proxy_command_secid(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_secid(shared_ptr ses, const std::u16string& args) { if (!args[0]) { - session.options.override_section_id = -1; - send_text_message(session.client_channel, u"$C6Override section ID\nremoved"); + ses->options.override_section_id = -1; + send_text_message(ses->client_channel, u"$C6Override section ID\nremoved"); } else { uint8_t new_secid = section_id_for_name(args); if (new_secid == 0xFF) { - send_text_message(session.client_channel, u"$C6Invalid section ID"); + send_text_message(ses->client_channel, u"$C6Invalid section ID"); } else { - session.options.override_section_id = new_secid; - send_text_message(session.client_channel, u"$C6Override section ID\nset"); + ses->options.override_section_id = new_secid; + send_text_message(ses->client_channel, u"$C6Override section ID\nset"); } } } -static void server_command_rand(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_rand(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, false); if (!args[0]) { @@ -652,19 +641,18 @@ static void server_command_rand(shared_ptr, shared_ptr l, } } -static void proxy_command_rand(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_rand(shared_ptr ses, const std::u16string& args) { if (!args[0]) { - session.options.override_random_seed = -1; - send_text_message(session.client_channel, u"$C6Override seed\nremoved"); + ses->options.override_random_seed = -1; + send_text_message(ses->client_channel, u"$C6Override seed\nremoved"); } else { - session.options.override_random_seed = stoul(encode_sjis(args), 0, 16); - send_text_message(session.client_channel, u"$C6Override seed\nset"); + ses->options.override_random_seed = stoul(encode_sjis(args), 0, 16); + send_text_message(ses->client_channel, u"$C6Override seed\nset"); } } -static void server_command_password(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_password(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); @@ -680,8 +668,8 @@ static void server_command_password(shared_ptr, shared_ptr l } } -static void server_command_toggle_spectator_flag(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_toggle_spectator_flag(shared_ptr c, const std::u16string&) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); check_is_ep3(c, true); @@ -707,8 +695,8 @@ static void server_command_toggle_spectator_flag(shared_ptr, shared } } -static void server_command_min_level(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_min_level(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); @@ -718,8 +706,8 @@ static void server_command_min_level(shared_ptr, shared_ptr l->min_level + 1); } -static void server_command_max_level(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_max_level(shared_ptr c, const std::u16string& args) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); @@ -738,8 +726,9 @@ static void server_command_max_level(shared_ptr, shared_ptr //////////////////////////////////////////////////////////////////////////////// // Character commands -static void server_command_edit(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_edit(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, false); check_version(c, GameVersion::BB); @@ -829,15 +818,15 @@ static void server_command_edit(shared_ptr s, shared_ptr l, } // TODO: implement this (and make sure the bank name is filesystem-safe) -/* static void server_command_change_bank(shared_ptr, shared_ptr, - shared_ptr c, const std::u16string&) { +/* static void server_command_change_bank(shared_ptr c, const std::u16string&) { check_version(c, GameVersion::BB); ... } */ // TODO: This can be implemented on the proxy server too. -static void server_command_convert_char_to_bb(shared_ptr s, - shared_ptr l, shared_ptr c, const std::u16string& args) { +static void server_command_convert_char_to_bb(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, false); check_not_version(c, GameVersion::BB); @@ -884,8 +873,9 @@ static string name_for_client(shared_ptr c) { return "Player"; } -static void server_command_silence(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_silence(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_privileges(c, Privilege::SILENCE_USER); auto target = s->find_client(&args); @@ -906,8 +896,9 @@ static void server_command_silence(shared_ptr s, shared_ptr target->can_chat ? "un" : ""); } -static void server_command_kick(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_kick(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_privileges(c, Privilege::KICK_USER); auto target = s->find_client(&args); @@ -928,8 +919,9 @@ static void server_command_kick(shared_ptr s, shared_ptr l, send_text_message_printf(l, "$C6%s kicked off", target_name.c_str()); } -static void server_command_ban(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string& args) { +static void server_command_ban(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_privileges(c, Privilege::BAN_USER); u16string args_str(args); @@ -981,8 +973,9 @@ static void server_command_ban(shared_ptr s, shared_ptr l, //////////////////////////////////////////////////////////////////////////////// // Cheat commands -static void server_command_warp( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args, bool is_warpall) { +static void server_command_warp(shared_ptr c, const std::u16string& args, bool is_warpall) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); @@ -1006,43 +999,40 @@ static void server_command_warp( } } -static void server_command_warpme( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { - server_command_warp(s, l, c, args, false); +static void server_command_warpme(shared_ptr c, const std::u16string& args) { + server_command_warp(c, args, false); } -static void server_command_warpall( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { - server_command_warp(s, l, c, args, true); +static void server_command_warpall(shared_ptr c, const std::u16string& args) { + server_command_warp(c, args, true); } -static void proxy_command_warp( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args, bool is_warpall) { +static void proxy_command_warp(shared_ptr ses, const std::u16string& args, bool is_warpall) { + auto s = ses->require_server_state(); check_cheats_enabled(s); - if (!session.is_in_game) { - send_text_message(session.client_channel, u"$C6You must be in a\ngame to use this\ncommand"); + if (!ses->is_in_game) { + send_text_message(ses->client_channel, u"$C6You must be in a\ngame to use this\ncommand"); return; } uint32_t area = stoul(encode_sjis(args), nullptr, 0); - send_warp(session.client_channel, session.lobby_client_id, area, !is_warpall); + send_warp(ses->client_channel, ses->lobby_client_id, area, !is_warpall); if (is_warpall) { - send_warp(session.server_channel, session.lobby_client_id, area, false); + send_warp(ses->server_channel, ses->lobby_client_id, area, false); } - session.area = area; + ses->area = area; } -static void proxy_command_warpme( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { - proxy_command_warp(s, session, args, false); +static void proxy_command_warpme(shared_ptr ses, const std::u16string& args) { + proxy_command_warp(ses, args, false); } -static void proxy_command_warpall( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { - proxy_command_warp(s, session, args, true); +static void proxy_command_warpall(shared_ptr ses, const std::u16string& args) { + proxy_command_warp(ses, args, true); } -static void server_command_next(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_next(shared_ptr c, const std::u16string&) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); @@ -1053,20 +1043,20 @@ static void server_command_next(shared_ptr s, shared_ptr l, send_warp(c, (c->area + 1) % limit, true); } -static void proxy_command_next( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_next(shared_ptr ses, const std::u16string&) { + auto s = ses->require_server_state(); check_cheats_enabled(s); - if (!session.is_in_game) { - send_text_message(session.client_channel, u"$C6You must be in a\ngame to use this\ncommand"); + if (!ses->is_in_game) { + send_text_message(ses->client_channel, u"$C6You must be in a\ngame to use this\ncommand"); return; } - session.area++; - send_warp(session.client_channel, session.lobby_client_id, session.area, true); + ses->area++; + send_warp(ses->client_channel, ses->lobby_client_id, ses->area, true); } -static void server_command_what(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_what(shared_ptr c, const std::u16string&) { + auto l = c->require_lobby(); check_is_game(l, true); if (!episode_has_arpg_semantics(l->episode)) { @@ -1100,26 +1090,25 @@ static void server_command_what(shared_ptr, shared_ptr l, } } -static void server_command_song(shared_ptr, shared_ptr, - shared_ptr c, const std::u16string& args) { +static void server_command_song(shared_ptr c, const std::u16string& args) { check_is_ep3(c, true); uint32_t song = stoul(encode_sjis(args), nullptr, 0); send_ep3_change_music(c->channel, song); } -static void proxy_command_song(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_song(shared_ptr ses, const std::u16string& args) { int32_t song = stol(encode_sjis(args), nullptr, 0); if (song < 0) { song = -song; - send_ep3_change_music(session.server_channel, song); + send_ep3_change_music(ses->server_channel, song); } - send_ep3_change_music(session.client_channel, song); + send_ep3_change_music(ses->client_channel, song); } -static void server_command_infinite_hp( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { +static void server_command_infinite_hp(shared_ptr c, const std::u16string&) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); @@ -1128,16 +1117,17 @@ static void server_command_infinite_hp( c->options.infinite_hp ? "enabled" : "disabled"); } -static void proxy_command_infinite_hp( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_infinite_hp(shared_ptr ses, const std::u16string&) { + auto s = ses->require_server_state(); check_cheats_enabled(s); - session.options.infinite_hp = !session.options.infinite_hp; - send_text_message_printf(session.client_channel, "$C6Infinite HP %s", - session.options.infinite_hp ? "enabled" : "disabled"); + ses->options.infinite_hp = !ses->options.infinite_hp; + send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", + ses->options.infinite_hp ? "enabled" : "disabled"); } -static void server_command_infinite_tp( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { +static void server_command_infinite_tp(shared_ptr c, const std::u16string&) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); @@ -1146,16 +1136,17 @@ static void server_command_infinite_tp( c->options.infinite_tp ? "enabled" : "disabled"); } -static void proxy_command_infinite_tp( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_infinite_tp(shared_ptr ses, const std::u16string&) { + auto s = ses->require_server_state(); check_cheats_enabled(s); - session.options.infinite_tp = !session.options.infinite_tp; - send_text_message_printf(session.client_channel, "$C6Infinite TP %s", - session.options.infinite_tp ? "enabled" : "disabled"); + ses->options.infinite_tp = !ses->options.infinite_tp; + send_text_message_printf(ses->client_channel, "$C6Infinite TP %s", + ses->options.infinite_tp ? "enabled" : "disabled"); } -static void server_command_switch_assist( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string&) { +static void server_command_switch_assist(shared_ptr c, const std::u16string&) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); @@ -1164,24 +1155,25 @@ static void server_command_switch_assist( c->options.switch_assist ? "enabled" : "disabled"); } -static void proxy_command_switch_assist( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string&) { +static void proxy_command_switch_assist(shared_ptr ses, const std::u16string&) { + auto s = ses->require_server_state(); check_cheats_enabled(s); - session.options.switch_assist = !session.options.switch_assist; - send_text_message_printf(session.client_channel, "$C6Switch assist %s", - session.options.switch_assist ? "enabled" : "disabled"); + ses->options.switch_assist = !ses->options.switch_assist; + send_text_message_printf(ses->client_channel, "$C6Switch assist %s", + ses->options.switch_assist ? "enabled" : "disabled"); } -static void server_command_drop(shared_ptr, shared_ptr l, - shared_ptr c, const std::u16string&) { +static void server_command_drop(shared_ptr c, const std::u16string&) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_leader(l, c); l->flags ^= Lobby::Flag::DROPS_ENABLED; send_text_message_printf(l, "Drops %s", (l->flags & Lobby::Flag::DROPS_ENABLED) ? "enabled" : "disabled"); } -static void server_command_item( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { +static void server_command_item(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_cheats_enabled(s, l); @@ -1196,21 +1188,21 @@ static void server_command_item( send_text_message(c, u"$C7Item created:\n" + decode_sjis(name)); } -static void proxy_command_item( - shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { +static void proxy_command_item(shared_ptr ses, const std::u16string& args) { + auto s = ses->require_server_state(); check_cheats_enabled(s); - if (session.version == GameVersion::BB) { - send_text_message(session.client_channel, + if (ses->version == GameVersion::BB) { + send_text_message(ses->client_channel, u"$C6This command cannot\nbe used on the proxy\nserver in BB games"); return; } - if (!session.is_in_game) { - send_text_message(session.client_channel, + if (!ses->is_in_game) { + send_text_message(ses->client_channel, u"$C6You must be in\na game to use this\ncommand"); return; } - if (session.lobby_client_id != session.leader_client_id) { - send_text_message(session.client_channel, + if (ses->lobby_client_id != ses->leader_client_id) { + send_text_message(ses->client_channel, u"$C6You must be the\nleader to use this\ncommand"); return; } @@ -1222,27 +1214,27 @@ static void proxy_command_item( item.data.id = random_object(); if (set_drop) { - session.next_drop_item = item; + ses->next_drop_item = item; - string name = session.next_drop_item.data.name(true); - send_text_message(session.client_channel, u"$C7Next drop:\n" + decode_sjis(name)); + string name = ses->next_drop_item.data.name(true); + send_text_message(ses->client_channel, u"$C7Next drop:\n" + decode_sjis(name)); } else { - send_drop_stacked_item(session.client_channel, item.data, session.area, session.x, session.z); - send_drop_stacked_item(session.server_channel, item.data, session.area, session.x, session.z); + send_drop_stacked_item(ses->client_channel, item.data, ses->area, ses->x, ses->z); + send_drop_stacked_item(ses->server_channel, item.data, ses->area, ses->x, ses->z); string name = item.data.name(true); - send_text_message(session.client_channel, u"$C7Item created:\n" + decode_sjis(name)); + send_text_message(ses->client_channel, u"$C7Item created:\n" + decode_sjis(name)); } } -static void server_command_enable_ep3_battle_debug_menu( - shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { +static void server_command_enable_ep3_battle_debug_menu(shared_ptr c, const std::u16string& args) { if (!c->options.debug) { - send_text_message(l, u"$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + send_text_message(c, u"$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); return; } + auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); if (l->episode != Episode::EP3) { @@ -1265,11 +1257,12 @@ static void server_command_enable_ep3_battle_debug_menu( } } -static void server_command_ep3_infinite_time( - shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { +static void server_command_ep3_infinite_time(shared_ptr c, const std::u16string&) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); check_is_leader(l, c); + if (l->episode != Episode::EP3) { throw logic_error("non-Ep3 client in Ep3 game"); } @@ -1287,8 +1280,9 @@ static void server_command_ep3_infinite_time( send_text_message(l, infinite_time_enabled ? u"$C6Infinite time enabled" : u"$C6Infinite time disabled"); } -static void server_command_ep3_unset_field_character( - shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { +static void server_command_ep3_unset_field_character(shared_ptr c, const std::u16string& args) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); check_cheats_enabled(s, l); @@ -1309,8 +1303,8 @@ static void server_command_ep3_unset_field_character( l->ep3_server->force_destroy_field_character(c->lobby_client_id, index); } -static void server_command_surrender( - shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { +static void server_command_surrender(shared_ptr c, const std::u16string&) { + auto l = c->require_lobby(); check_is_game(l, true); check_is_ep3(c, true); if (l->episode != Episode::EP3) { @@ -1326,15 +1320,16 @@ static void server_command_surrender( } string name = encode_sjis(c->game_data.player()->disp.name); send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str()); + for (const auto& watcher_l : l->watcher_lobbies) { + send_text_message_printf(watcher_l, "$C6%s has\nsurrendered", name.c_str()); + } l->ep3_server->force_battle_result(c->lobby_client_id, false); } //////////////////////////////////////////////////////////////////////////////// -typedef void (*server_handler_t)(shared_ptr s, shared_ptr l, - shared_ptr c, const std::u16string& args); -typedef void (*proxy_handler_t)(shared_ptr, - ProxyServer::LinkedSession& session, const std::u16string& args); +typedef void (*server_handler_t)(shared_ptr c, const std::u16string& args); +typedef void (*proxy_handler_t)(shared_ptr ses, const std::u16string& args); struct ChatCommandDefinition { server_handler_t server_handler; proxy_handler_t proxy_handler; @@ -1406,8 +1401,7 @@ struct SplitCommand { // This function is called every time any player sends a chat beginning with a // dollar sign. It is this function's responsibility to see if the chat is a // command, and to execute the command and block the chat if it is. -void on_chat_command(std::shared_ptr s, std::shared_ptr l, - std::shared_ptr c, const std::u16string& text) { +void on_chat_command(std::shared_ptr c, const std::u16string& text) { SplitCommand cmd(text); const ChatCommandDefinition* def = nullptr; @@ -1422,7 +1416,7 @@ void on_chat_command(std::shared_ptr s, std::shared_ptr l, send_text_message(c, u"$C6Command not available\non game server"); } else { try { - def->server_handler(s, l, c, cmd.args); + def->server_handler(c, cmd.args); } catch (const precondition_failed& e) { send_text_message(c, e.what()); } catch (const exception& e) { @@ -1431,27 +1425,26 @@ void on_chat_command(std::shared_ptr s, std::shared_ptr l, } } -void on_chat_command(std::shared_ptr s, - ProxyServer::LinkedSession& session, const std::u16string& text) { +void on_chat_command(shared_ptr ses, const std::u16string& text) { SplitCommand cmd(text); const ChatCommandDefinition* def = nullptr; try { def = &chat_commands.at(cmd.name); } catch (const out_of_range&) { - send_text_message(session.client_channel, u"$C6Unknown command"); + send_text_message(ses->client_channel, u"$C6Unknown command"); return; } if (!def->proxy_handler) { - send_text_message(session.client_channel, u"$C6Command not available\non proxy server"); + send_text_message(ses->client_channel, u"$C6Command not available\non proxy server"); } else { try { - def->proxy_handler(s, session, cmd.args); + def->proxy_handler(ses, cmd.args); } catch (const precondition_failed& e) { - send_text_message(session.client_channel, e.what()); + send_text_message(ses->client_channel, e.what()); } catch (const exception& e) { - send_text_message_printf(session.client_channel, "$C6Failed:\n%s", e.what()); + send_text_message_printf(ses->client_channel, "$C6Failed:\n%s", e.what()); } } } diff --git a/src/ChatCommands.hh b/src/ChatCommands.hh index 7d669ef0..c79172bb 100644 --- a/src/ChatCommands.hh +++ b/src/ChatCommands.hh @@ -10,7 +10,5 @@ #include "ProxyServer.hh" #include "ServerState.hh" -void on_chat_command(std::shared_ptr s, std::shared_ptr l, - std::shared_ptr c, const std::u16string& text); -void on_chat_command(std::shared_ptr s, - ProxyServer::LinkedSession& session, const std::u16string& text); +void on_chat_command(std::shared_ptr c, const std::u16string& text); +void on_chat_command(shared_ptr ses, const std::u16string& text); diff --git a/src/Client.cc b/src/Client.cc index 8b9d5268..be9805e8 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -12,6 +12,7 @@ #include #include "Loggers.hh" +#include "Server.hh" #include "Version.hh" using namespace std; @@ -42,10 +43,12 @@ ClientOptions::ClientOptions() function_call_return_value(-1) {} Client::Client( + shared_ptr server, struct bufferevent* bev, GameVersion version, ServerBehavior server_behavior) - : id(next_id++), + : 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)), @@ -60,7 +63,6 @@ Client::Client( x(0.0f), z(0.0f), area(0), - lobby_id(0), lobby_client_id(0), lobby_arrow_color(0), preferred_lobby_id(-1), @@ -69,6 +71,16 @@ Client::Client( bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST, &Client::dispatch_save_game_data, this), event_free), + send_ping_event( + event_new( + bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST, + &Client::dispatch_send_ping, this), + event_free), + idle_timeout_event( + event_new( + bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST, + &Client::dispatch_idle_timeout, this), + event_free), card_battle_table_number(-1), card_battle_table_seat_number(0), card_battle_table_seat_state(0), @@ -83,6 +95,7 @@ Client::Client( struct timeval tv = usecs_to_timeval(60000000); // 1 minute event_add(this->save_game_data_event.get(), &tv); } + this->reschedule_ping_and_timeout_events(); this->log.info("Created"); } @@ -98,6 +111,13 @@ Client::~Client() { this->log.info("Deleted"); } +void Client::reschedule_ping_and_timeout_events() { + struct timeval ping_tv = usecs_to_timeval(30000000); // 30 seconds + event_add(this->send_ping_event.get(), &ping_tv); + struct timeval idle_tv = usecs_to_timeval(60000000); // 1 minute + event_add(this->idle_timeout_event.get(), &idle_tv); +} + QuestScriptVersion Client::quest_version() const { switch (this->version()) { case GameVersion::DC: @@ -135,6 +155,22 @@ void Client::set_license(shared_ptr l) { } } +shared_ptr Client::require_server_state() const { + auto server = this->server.lock(); + if (!server) { + throw logic_error("server is deleted"); + } + return server->get_state(); +} + +shared_ptr Client::require_lobby() const { + auto l = this->lobby.lock(); + if (!l) { + throw runtime_error("client not in any lobby"); + } + return l; +} + ClientConfig Client::export_config() const { ClientConfig cc; cc.magic = CLIENT_CONFIG_MAGIC; @@ -186,3 +222,31 @@ void Client::save_game_data() { this->game_data.save_player_data(); } } + +void Client::dispatch_send_ping(evutil_socket_t, short, void* ctx) { + reinterpret_cast(ctx)->send_ping(); +} + +void Client::send_ping() { + if (this->version() == GameVersion::PATCH) { + throw logic_error("send_ping called for patch client"); + } + this->log.info("Sending ping command"); + // The game doesn't use this timestamp; we only use it for debugging purposes + be_uint64_t timestamp = now(); + this->channel.send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t)); +} + +void Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) { + reinterpret_cast(ctx)->idle_timeout(); +} + +void Client::idle_timeout() { + this->log.info("Idle timeout expired"); + auto s = this->server.lock(); + if (s) { + s->disconnect_client(this->shared_from_this()); + } else { + this->log.info("Server is deleted; cannot disconnect client"); + } +} diff --git a/src/Client.hh b/src/Client.hh index 5e67bc83..b5bc4cfa 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -20,6 +20,9 @@ extern const uint64_t CLIENT_CONFIG_MAGIC; +class Server; +struct Lobby; + struct ClientOptions { // Options used on both game and proxy server bool switch_assist; @@ -47,7 +50,7 @@ struct ClientOptions { ClientOptions(); }; -struct Client { +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 @@ -113,6 +116,8 @@ struct Client { UNUSED_FLAG_BITS = 0xFF010000, }; + std::weak_ptr server; + std::weak_ptr server_state; uint64_t id; PrefixedLogger log; @@ -144,13 +149,15 @@ struct Client { ClientOptions options; float x; float z; - uint32_t area; // which area is the client in? - uint32_t lobby_id; // which lobby is this person in? - uint8_t lobby_client_id; // which client number is this person? - uint8_t lobby_arrow_color; // lobby arrow color ID + uint32_t area; + std::weak_ptr lobby; + uint8_t lobby_client_id; + uint8_t lobby_arrow_color; int64_t preferred_lobby_id; // <0 = no preference ClientGameData game_data; std::unique_ptr save_game_data_event; + std::unique_ptr send_ping_event; + std::unique_ptr idle_timeout_event; int16_t card_battle_table_number; uint16_t card_battle_table_seat_number; uint16_t card_battle_table_seat_state; @@ -170,9 +177,15 @@ struct Client { std::shared_ptr loading_dol_file; std::unordered_map> sending_files; - Client(struct bufferevent* bev, GameVersion version, ServerBehavior server_behavior); + Client( + shared_ptr server, + struct bufferevent* bev, + GameVersion version, + ServerBehavior server_behavior); ~Client(); + void reschedule_ping_and_timeout_events(); + inline GameVersion version() const { return this->channel.version; } @@ -180,6 +193,9 @@ struct Client { void set_license(std::shared_ptr l); + 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); @@ -187,4 +203,8 @@ struct Client { 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); + void send_ping(); + static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx); + void idle_timeout(); }; diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index e05b63b5..f6ea7b31 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -660,7 +660,7 @@ void Tournament::start() { } } -void Tournament::send_all_state_updates(shared_ptr s) const { +void Tournament::send_all_state_updates() const { for (const auto& team : this->teams) { for (const auto& player : team->players) { auto c = player.client.lock(); @@ -671,7 +671,7 @@ void Tournament::send_all_state_updates(shared_ptr s) const { (c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) && (c->ep3_tournament_team.lock() == team)) { - send_ep3_confirm_tournament_entry(s, c, this->shared_from_this()); + send_ep3_confirm_tournament_entry(c, this->shared_from_this()); } } } @@ -685,7 +685,7 @@ void Tournament::send_all_state_updates_on_deletion() const { (c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) && (c->ep3_tournament_team.lock() == team)) { - send_ep3_confirm_tournament_entry(nullptr, c, nullptr); + send_ep3_confirm_tournament_entry(c, nullptr); } } } @@ -874,7 +874,7 @@ shared_ptr TournamentIndex::team_for_serial_number(uint32_t se return nullptr; } -void TournamentIndex::link_client(shared_ptr s, shared_ptr c) { +void TournamentIndex::link_client(shared_ptr c) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { return; } @@ -887,7 +887,7 @@ void TournamentIndex::link_client(shared_ptr s, shared_ptr c->ep3_tournament_team = team; player.client = c; if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { - send_ep3_confirm_tournament_entry(s, c, tourn); + send_ep3_confirm_tournament_entry(c, tourn); } return; } @@ -896,14 +896,14 @@ void TournamentIndex::link_client(shared_ptr s, shared_ptr } else { c->ep3_tournament_team.reset(); if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { - send_ep3_confirm_tournament_entry(s, c, nullptr); + send_ep3_confirm_tournament_entry(c, nullptr); } } } void TournamentIndex::link_all_clients(std::shared_ptr s) { for (const auto& c_it : s->channel_to_client) { - this->link_client(s, c_it.second); + this->link_client(c_it.second); } } diff --git a/src/Episode3/Tournament.hh b/src/Episode3/Tournament.hh index 0fa47596..6a541e67 100644 --- a/src/Episode3/Tournament.hh +++ b/src/Episode3/Tournament.hh @@ -156,7 +156,7 @@ public: void start(); - void send_all_state_updates(std::shared_ptr s) const; + void send_all_state_updates() const; void send_all_state_updates_on_deletion() const; void print_bracket(FILE* stream) const; @@ -232,7 +232,7 @@ public: std::shared_ptr team_for_serial_number(uint32_t serial_number) const; - void link_client(std::shared_ptr s, std::shared_ptr c); + void link_client(std::shared_ptr c); void link_all_clients(std::shared_ptr s); private: diff --git a/src/Items.cc b/src/Items.cc index fdfa8cb7..e5e843a8 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -8,7 +8,9 @@ using namespace std; -void player_use_item(shared_ptr s, shared_ptr c, size_t item_index) { +void player_use_item(shared_ptr c, size_t item_index) { + auto s = c->require_server_state(); + // On PC (and presumably DC), the client sends a 6x29 after this to delete the // used item. On GC and later versions, this does not happen, so we should // delete the item here. @@ -152,8 +154,10 @@ void player_use_item(shared_ptr s, shared_ptr c, size_t ite item.data.data1.clear_after(3); should_delete_item = false; - auto l = s->find_lobby(c->lobby_id); - send_create_inventory_item(l, c, item.data); + auto l = c->lobby.lock(); + if (l) { + send_create_inventory_item(c, item.data); + } break; } } @@ -218,7 +222,7 @@ void player_use_item(shared_ptr s, shared_ptr c, size_t ite } } -void player_feed_mag(std::shared_ptr s, std::shared_ptr c, size_t mag_item_index, size_t fed_item_index) { +void player_feed_mag(std::shared_ptr c, size_t mag_item_index, size_t fed_item_index) { static const unordered_map result_index_for_fed_item({ {0x030000, 0}, // Monomate {0x030001, 1}, // Dimate @@ -233,6 +237,7 @@ void player_feed_mag(std::shared_ptr s, std::shared_ptr c, {0x030500, 10}, // Star Atomizer }); + auto s = c->require_server_state(); auto player = c->game_data.player(); auto& fed_item = player->inventory.items[fed_item_index]; auto& mag_item = player->inventory.items[mag_item_index]; diff --git a/src/Items.hh b/src/Items.hh index 5fd0e8eb..381a036d 100644 --- a/src/Items.hh +++ b/src/Items.hh @@ -9,5 +9,5 @@ #include "ServerState.hh" #include "StaticGameData.hh" -void player_use_item(std::shared_ptr s, std::shared_ptr c, size_t item_index); -void player_feed_mag(std::shared_ptr s, std::shared_ptr c, size_t mag_item_index, size_t fed_item_index); +void player_use_item(std::shared_ptr c, size_t item_index); +void player_feed_mag(std::shared_ptr c, size_t mag_item_index, size_t fed_item_index); diff --git a/src/Lobby.cc b/src/Lobby.cc index 16a4dbe7..b7e7f570 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -10,8 +10,9 @@ using namespace std; -Lobby::Lobby(uint32_t id) - : log(string_printf("[Lobby/%" PRIX32 "] ", id), lobby_log.min_level), +Lobby::Lobby(shared_ptr s, uint32_t id) + : server_state(s), + log(string_printf("[Lobby/%" PRIX32 "] ", id), lobby_log.min_level), lobby_id(id), min_level(0), max_level(0xFFFFFFFF), @@ -33,6 +34,14 @@ Lobby::Lobby(uint32_t id) } } +shared_ptr Lobby::require_server_state() const { + auto s = this->server_state.lock(); + if (!s) { + throw logic_error("server is deleted"); + } + return s; +} + void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) { for (size_t x = 0; x < this->max_clients; x++) { if (x == leaving_client_index) { @@ -102,7 +111,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { } c->lobby_client_id = index; - c->lobby_id = this->lobby_id; + c->lobby = this->weak_from_this(); // If there's no one else in the lobby, set the leader id as well size_t leader_index; @@ -162,11 +171,14 @@ void Lobby::remove_client(shared_ptr c) { this->clients[c->lobby_client_id] = nullptr; - // Unassign the client's lobby if it matches the current lobby's id (it may - // not match if the client was already added to another lobby - this can - // happen during the lobby change procedure) - if (c->lobby_id == this->lobby_id) { - c->lobby_id = 0; + // Unassign the client's lobby if it matches the current lobby (it may not + // match if the client was already added to another lobby - this can happen + // during the lobby change procedure) + { + auto c_lobby = c->lobby.lock(); + if (c_lobby.get() == this) { + c->lobby.reset(); + } } this->reassign_leader_on_client_departure(c->lobby_client_id); diff --git a/src/Lobby.hh b/src/Lobby.hh index 4a5f5ea9..7a50f80f 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -22,6 +22,8 @@ #include "StaticGameData.hh" #include "Text.hh" +struct ServerState; + struct Lobby : public std::enable_shared_from_this { enum Flag { GAME = 0x00000001, @@ -45,6 +47,7 @@ struct Lobby : public std::enable_shared_from_this { DEFAULT = 0x02000000, }; + std::weak_ptr server_state; PrefixedLogger log; uint32_t lobby_id; @@ -110,7 +113,13 @@ struct Lobby : public std::enable_shared_from_this { // Keys in this map are client_id std::unordered_map> clients_to_add; - explicit Lobby(uint32_t id); + Lobby(std::shared_ptr s, uint32_t id); + Lobby(const Lobby&) = delete; + Lobby(Lobby&&) = delete; + Lobby& operator=(const Lobby&) = delete; + Lobby& operator=(Lobby&&) = delete; + + std::shared_ptr require_server_state() const; inline bool is_game() const { return this->flags & Flag::GAME; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index c5cfa105..9f4a80a7 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -52,9 +52,10 @@ struct HandlerResult { int32_t new_command; int64_t new_flag; - HandlerResult(Type type) : type(type), - new_command(-1), - new_flag(-1) {} + HandlerResult(Type type) + : type(type), + new_command(-1), + new_flag(-1) {} HandlerResult(Type type, uint16_t new_command, uint32_t new_flag) : type(type), new_command(new_command), @@ -62,15 +63,14 @@ struct HandlerResult { }; typedef HandlerResult (*on_command_t)( - shared_ptr s, - ProxyServer::LinkedSession& session, + shared_ptr ses, uint16_t command, uint32_t flag, string& data); -static void forward_command(ProxyServer::LinkedSession& session, bool to_server, +static void forward_command(shared_ptr ses, bool to_server, uint16_t command, uint32_t flag, string& data, bool print_contents = true) { - auto& ch = to_server ? session.server_channel : session.client_channel; + auto& ch = to_server ? ses->server_channel : ses->client_channel; if (!ch.connected()) { proxy_server_log.warning("No endpoint is present; dropping command"); } else { @@ -79,12 +79,12 @@ static void forward_command(ProxyServer::LinkedSession& session, bool to_server, } static void check_implemented_subcommand( - ProxyServer::LinkedSession& session, const string& data) { + shared_ptr ses, const string& data) { if (data.size() < 4) { - session.log.warning("Received broadcast/target command with no contents"); + ses->log.warning("Received broadcast/target command with no contents"); } else { if (!subcommand_is_implemented(data[0])) { - session.log.warning("Received subcommand %02hhX which is not implemented on the server", + ses->log.warning("Received subcommand %02hhX which is not implemented on the server", data[0]); } } @@ -101,57 +101,51 @@ static void check_implemented_subcommand( // the command handler is for all versions (for example, the 97 handler is like // this). -static HandlerResult default_handler(shared_ptr, - ProxyServer::LinkedSession&, uint16_t, uint32_t, string&) { +static HandlerResult default_handler(shared_ptr, uint16_t, uint32_t, string&) { return HandlerResult::Type::FORWARD; } -static HandlerResult S_invalid(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string&) { - session.log.error("Server sent invalid command"); - string error_str = (session.version == GameVersion::BB) +static HandlerResult S_invalid(shared_ptr ses, uint16_t command, uint32_t flag, string&) { + ses->log.error("Server sent invalid command"); + string error_str = (ses->version == GameVersion::BB) ? string_printf("Server sent invalid\ncommand: %04hX %08" PRIX32, command, flag) : string_printf("Server sent invalid\ncommand: %02hX %02" PRIX32, command, flag); - session.send_to_game_server(error_str.c_str()); + ses->send_to_game_server(error_str.c_str()); return HandlerResult::Type::SUPPRESS; } -static HandlerResult C_05(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - session.disconnect_action = session.version == GameVersion::BB +static HandlerResult C_05(shared_ptr ses, uint16_t, uint32_t, string&) { + ses->disconnect_action = ses->version == GameVersion::BB ? ProxyServer::LinkedSession::DisconnectAction::MEDIUM_TIMEOUT : ProxyServer::LinkedSession::DisconnectAction::SHORT_TIMEOUT; return HandlerResult::Type::FORWARD; } -static HandlerResult C_1D(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - return session.options.suppress_client_pings +static HandlerResult C_1D(shared_ptr ses, uint16_t, uint32_t, string&) { + return ses->options.suppress_client_pings ? HandlerResult::Type::SUPPRESS : HandlerResult::Type::FORWARD; } -static HandlerResult S_1D(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (session.options.suppress_client_pings) { - session.server_channel.send(0x1D); +static HandlerResult S_1D(shared_ptr ses, uint16_t, uint32_t, string&) { + if (ses->options.suppress_client_pings) { + ses->server_channel.send(0x1D); return HandlerResult::Type::SUPPRESS; } else { return HandlerResult::Type::FORWARD; } } -static HandlerResult S_97(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string&) { +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 (session.newserv_client_config.cfg.flags & Client::Flag::SAVE_ENABLED) { - session.server_channel.send(0xB1, 0x00); + if (ses->newserv_client_config.cfg.flags & 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 - session.newserv_client_config.cfg.flags |= Client::Flag::SAVE_ENABLED; + ses->newserv_client_config.cfg.flags |= Client::Flag::SAVE_ENABLED; // Trap any 97 command that would have triggered cheat protection, and // always send 97 01 04 00 if (flag == 0) { @@ -161,14 +155,13 @@ static HandlerResult S_97(shared_ptr, } } -static HandlerResult C_G_9E(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (session.options.suppress_remote_login) { +static HandlerResult C_G_9E(shared_ptr ses, uint16_t, uint32_t, string&) { + if (ses->options.suppress_remote_login) { le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; - session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); + ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); - S_UpdateClientConfig_DC_PC_V3_04 cmd = {{0x00010000, session.license->serial_number, ClientConfig()}}; - session.client_channel.send(0x04, 0x00, &cmd, sizeof(cmd)); + S_UpdateClientConfig_DC_PC_V3_04 cmd = {{0x00010000, ses->license->serial_number, ClientConfig()}}; + ses->client_channel.send(0x04, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -177,52 +170,50 @@ static HandlerResult C_G_9E(shared_ptr, } } -static HandlerResult S_G_9A(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (!session.license || session.options.suppress_remote_login) { +static HandlerResult S_G_9A(shared_ptr ses, uint16_t, uint32_t, string&) { + if (!ses->license || ses->options.suppress_remote_login) { return HandlerResult::Type::FORWARD; } C_LoginExtended_GC_9E cmd; - if (session.remote_guild_card_number < 0) { + if (ses->remote_guild_card_number < 0) { cmd.player_tag = 0xFFFF0000; cmd.guild_card_number = 0xFFFFFFFF; } else { cmd.player_tag = 0x00010000; - cmd.guild_card_number = session.remote_guild_card_number; + cmd.guild_card_number = ses->remote_guild_card_number; } cmd.unused1 = 0; cmd.unused2 = 0; - cmd.sub_version = session.sub_version; - cmd.is_extended = (session.remote_guild_card_number < 0) ? 1 : 0; - cmd.language = session.language; - cmd.serial_number = string_printf("%08" PRIX32 "", session.license->serial_number); - cmd.access_key = session.license->access_key; + cmd.sub_version = ses->sub_version; + cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0; + cmd.language = ses->language; + cmd.serial_number = string_printf("%08" PRIX32 "", ses->license->serial_number); + cmd.access_key = ses->license->access_key; cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - if (session.options.blank_name) { + if (ses->options.blank_name) { cmd.name = " "; } else { - cmd.name = session.character_name; + cmd.name = ses->character_name; } - cmd.client_config.data = session.remote_client_config_data; + cmd.client_config.data = 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 - session.server_channel.send( + ses->server_channel.send( 0x9E, 0x01, &cmd, cmd.is_extended ? sizeof(C_LoginExtended_GC_9E) : sizeof(C_Login_GC_9E)); return HandlerResult::Type::SUPPRESS; } static HandlerResult S_V123P_02_17( - shared_ptr s, - ProxyServer::LinkedSession& session, + shared_ptr ses, uint16_t command, uint32_t flag, string& data) { - if (session.version == GameVersion::PATCH && command == 0x17) { + if (ses->version == GameVersion::PATCH && command == 0x17) { throw invalid_argument("patch server sent 17 server init"); } @@ -230,43 +221,43 @@ static HandlerResult S_V123P_02_17( // after_message than newserv does, so don't require it const auto& cmd = check_size_t(data, 0xFFFF); - if (!session.license) { - session.log.info("No license in linked session"); + if (!ses->license) { + ses->log.info("No license in linked session"); // We have to forward the command before setting up encryption, so the // client will be able to understand it. - forward_command(session, false, command, flag, data); + forward_command(ses, false, command, flag, data); - if ((session.version == GameVersion::GC) || - (session.version == GameVersion::XB)) { - session.server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); - session.server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); - session.client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key)); - session.client_channel.crypt_out.reset(new PSOV3Encryption(cmd.server_key)); + if ((ses->version == GameVersion::GC) || + (ses->version == GameVersion::XB)) { + ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); + ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); + ses->client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key)); + ses->client_channel.crypt_out.reset(new PSOV3Encryption(cmd.server_key)); } else { // DC, PC, or patch server (they all use V2 encryption) - session.server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); - session.server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); - session.client_channel.crypt_in.reset(new PSOV2Encryption(cmd.client_key)); - session.client_channel.crypt_out.reset(new PSOV2Encryption(cmd.server_key)); + ses->server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); + ses->server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); + ses->client_channel.crypt_in.reset(new PSOV2Encryption(cmd.client_key)); + ses->client_channel.crypt_out.reset(new PSOV2Encryption(cmd.server_key)); } return HandlerResult::Type::SUPPRESS; } - session.log.info("Existing license in linked session"); + ses->log.info("Existing license in linked session"); // This isn't forwarded to the client, so don't recreate the client's crypts - switch (session.version) { + switch (ses->version) { case GameVersion::DC: case GameVersion::PC: case GameVersion::PATCH: - session.server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); - session.server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); + ses->server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); + ses->server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); break; case GameVersion::GC: case GameVersion::XB: - session.server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); - session.server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); + ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); + ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key)); break; default: throw logic_error("unsupported version"); @@ -276,113 +267,113 @@ static HandlerResult S_V123P_02_17( // because it believes it already did (when it was in an unlinked session, or // in the patch server case, during the current session due to a hidden // redirect). - if (session.version == GameVersion::PATCH) { - session.server_channel.send(0x02); + if (ses->version == GameVersion::PATCH) { + ses->server_channel.send(0x02); return HandlerResult::Type::SUPPRESS; - } else if ((session.version == GameVersion::DC) || - (session.version == GameVersion::PC)) { - if (session.newserv_client_config.cfg.flags & Client::Flag::IS_DC_V1) { + } else if ((ses->version == GameVersion::DC) || + (ses->version == GameVersion::PC)) { + if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_DC_V1) { if (command == 0x17) { C_LoginV1_DC_PC_V3_90 cmd; cmd.serial_number = string_printf("%08" PRIX32 "", - session.license->serial_number); - cmd.access_key = session.license->access_key; + ses->license->serial_number); + cmd.access_key = ses->license->access_key; cmd.access_key.clear_after(8); - session.server_channel.send(0x90, 0x00, &cmd, sizeof(cmd)); + ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } else { C_LoginV1_DC_93 cmd; - if (session.remote_guild_card_number < 0) { + if (ses->remote_guild_card_number < 0) { cmd.player_tag = 0xFFFF0000; cmd.guild_card_number = 0xFFFFFFFF; } else { cmd.player_tag = 0x00010000; - cmd.guild_card_number = session.remote_guild_card_number; + cmd.guild_card_number = ses->remote_guild_card_number; } cmd.unknown_a1 = 0; cmd.unknown_a2 = 0; - cmd.sub_version = session.sub_version; + cmd.sub_version = ses->sub_version; cmd.is_extended = 0; - cmd.language = session.language; + cmd.language = ses->language; cmd.serial_number = string_printf("%08" PRIX32 "", - session.license->serial_number); - cmd.access_key = session.license->access_key; + ses->license->serial_number); + cmd.access_key = ses->license->access_key; cmd.access_key.clear_after(8); - cmd.hardware_id = session.hardware_id; - cmd.name = session.character_name; - session.server_channel.send(0x93, 0x00, &cmd, sizeof(cmd)); + cmd.hardware_id = ses->hardware_id; + cmd.name = ses->character_name; + ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } } else { // DCv2 or PC if (command == 0x17) { C_Login_DC_PC_V3_9A cmd; - if (session.remote_guild_card_number < 0) { + if (ses->remote_guild_card_number < 0) { cmd.player_tag = 0xFFFF0000; cmd.guild_card_number = 0xFFFFFFFF; } else { cmd.player_tag = 0x00010000; - cmd.guild_card_number = session.remote_guild_card_number; + cmd.guild_card_number = ses->remote_guild_card_number; } - cmd.sub_version = session.sub_version; + cmd.sub_version = ses->sub_version; cmd.serial_number = string_printf("%08" PRIX32 "", - session.license->serial_number); - cmd.access_key = session.license->access_key; + ses->license->serial_number); + cmd.access_key = ses->license->access_key; cmd.access_key.clear_after(8); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; // TODO: We probably should set email_address, but we currently don't // keep that value anywhere in the session object, nor is it saved in // the License object. - session.server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd)); + ses->server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } else { C_Login_DC_PC_GC_9D cmd; - if (session.remote_guild_card_number < 0) { + if (ses->remote_guild_card_number < 0) { cmd.player_tag = 0xFFFF0000; cmd.guild_card_number = 0xFFFFFFFF; } else { cmd.player_tag = 0x00010000; - cmd.guild_card_number = session.remote_guild_card_number; + cmd.guild_card_number = ses->remote_guild_card_number; } cmd.unused1 = 0; cmd.unused2 = 0; - cmd.sub_version = session.sub_version; + cmd.sub_version = ses->sub_version; cmd.is_extended = 0; - cmd.language = session.language; + cmd.language = ses->language; cmd.serial_number = string_printf("%08" PRIX32 "", - session.license->serial_number); - cmd.access_key = session.license->access_key; + ses->license->serial_number); + cmd.access_key = ses->license->access_key; cmd.access_key.clear_after(8); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - if (session.options.blank_name) { + if (ses->options.blank_name) { cmd.name = " "; } else { - cmd.name = session.character_name; + cmd.name = ses->character_name; } - session.server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd)); + ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } } - } else if (session.version == GameVersion::GC) { + } else if (ses->version == GameVersion::GC) { if (command == 0x17) { C_VerifyLicense_V3_DB cmd; cmd.serial_number = string_printf("%08" PRIX32 "", - session.license->serial_number); - cmd.access_key = session.license->access_key; - cmd.sub_version = session.sub_version; + ses->license->serial_number); + cmd.access_key = ses->license->access_key; + cmd.sub_version = ses->sub_version; cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - cmd.password = session.license->gc_password; - session.server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd)); + cmd.password = ses->license->gc_password; + ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; - } else if (session.options.suppress_remote_login) { + } else if (ses->options.suppress_remote_login) { uint32_t guild_card_number; - if (session.remote_guild_card_number >= 0) { - guild_card_number = session.remote_guild_card_number; + if (ses->remote_guild_card_number >= 0) { + guild_card_number = ses->remote_guild_card_number; log_info("Using Guild Card number %" PRIu32 " from session", guild_card_number); } else { guild_card_number = random_object(); @@ -401,28 +392,28 @@ static HandlerResult S_V123P_02_17( cmd.guild_card_number = guild_card_number; cmd.unused1 = 0; cmd.unused2 = 0; - cmd.sub_version = session.sub_version; + cmd.sub_version = ses->sub_version; cmd.is_extended = 0; - cmd.language = session.language; + cmd.language = ses->language; cmd.serial_number = string_printf("%08" PRIX32, fake_serial_number); cmd.access_key = fake_access_key_str; cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - if (session.options.blank_name) { + if (ses->options.blank_name) { cmd.name = " "; } else { - cmd.name = session.character_name; + cmd.name = ses->character_name; } - cmd.client_config.data = session.remote_client_config_data; - session.server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E)); + cmd.client_config.data = ses->remote_client_config_data; + ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E)); return HandlerResult::Type::SUPPRESS; } else { // For command 02, send the same as if we had received 9A from the server - return S_G_9A(s, session, command, flag, data); + return S_G_9A(ses, command, flag, data); } - } else if (session.version == GameVersion::XB) { + } else if (ses->version == GameVersion::XB) { throw runtime_error("xbox licenses are not implemented"); } else { @@ -430,16 +421,15 @@ static HandlerResult S_V123P_02_17( } } -static HandlerResult S_B_03(shared_ptr s, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_B_03(shared_ptr ses, uint16_t, uint32_t, string& data) { // Most servers don't include after_message or have a shorter after_message // than newserv does, so don't require it const auto& cmd = check_size_t(data, 0xFFFF); // If the session has a detector crypt, then it was resumed from an unlinked // session, during which we already sent an 03 command. - if (session.detector_crypt.get()) { - if (session.login_command_bb.empty()) { + if (ses->detector_crypt.get()) { + if (ses->login_command_bb.empty()) { throw logic_error("linked BB session does not have a saved login command"); } @@ -448,17 +438,17 @@ static HandlerResult S_B_03(shared_ptr s, // being able to try all the crypts it knows to detect what type the client // uses, but the client can't do this since it sends the first encrypted // data on the connection. - session.server_channel.crypt_in.reset(new PSOBBMultiKeyImitatorEncryption( - session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false)); - session.server_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( - session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false)); + ses->server_channel.crypt_in.reset(new PSOBBMultiKeyImitatorEncryption( + ses->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false)); + ses->server_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( + ses->detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false)); - // Forward the login command we saved during the unlinked session. - if (session.enable_remote_ip_crc_patch && (session.login_command_bb.size() >= 0x98)) { - *reinterpret_cast(session.login_command_bb.data() + 0x94) = - session.remote_ip_crc ^ (1309539928UL + 1248334810UL); + // Forward the login command we saved during the unlinked ses-> + if (ses->enable_remote_ip_crc_patch && (ses->login_command_bb.size() >= 0x98)) { + *reinterpret_cast(ses->login_command_bb.data() + 0x94) = + ses->remote_ip_crc ^ (1309539928UL + 1248334810UL); } - session.server_channel.send(0x93, 0x00, session.login_command_bb); + ses->server_channel.send(0x93, 0x00, ses->login_command_bb); return HandlerResult::Type::SUPPRESS; @@ -468,29 +458,31 @@ static HandlerResult S_B_03(shared_ptr s, } else { // Forward the command to the client before setting up the crypts, so the // client receives the unencrypted data - session.client_channel.send(0x03, 0x00, data); + ses->client_channel.send(0x03, 0x00, data); - session.detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption( - s->bb_private_keys, bb_crypt_initial_client_commands, cmd.client_key.data(), sizeof(cmd.client_key))); - session.client_channel.crypt_in = session.detector_crypt; - session.client_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( - session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true)); - session.server_channel.crypt_in.reset(new PSOBBMultiKeyImitatorEncryption( - session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false)); - session.server_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( - session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false)); + ses->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption( + ses->require_server_state()->bb_private_keys, + bb_crypt_initial_client_commands, + cmd.client_key.data(), + sizeof(cmd.client_key))); + ses->client_channel.crypt_in = ses->detector_crypt; + ses->client_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( + ses->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true)); + ses->server_channel.crypt_in.reset(new PSOBBMultiKeyImitatorEncryption( + ses->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false)); + ses->server_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( + ses->detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false)); // We already forwarded the command, so don't do so again return HandlerResult::Type::SUPPRESS; } } -static HandlerResult S_V123_04(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +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)) { le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; - session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); + ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); return HandlerResult::Type::SUPPRESS; } @@ -504,18 +496,18 @@ static HandlerResult S_V123_04(shared_ptr, // remote server so the client doesn't see it change. If this is an unlicensed // session, then the client never received a guild card number from newserv // anyway, so we can let the client see the number from the remote server. - bool had_guild_card_number = (session.remote_guild_card_number >= 0); - if (session.remote_guild_card_number != cmd.guild_card_number) { - session.remote_guild_card_number = cmd.guild_card_number; - session.log.info("Remote guild card number set to %" PRId64, - session.remote_guild_card_number); + bool had_guild_card_number = (ses->remote_guild_card_number >= 0); + if (ses->remote_guild_card_number != cmd.guild_card_number) { + ses->remote_guild_card_number = cmd.guild_card_number; + ses->log.info("Remote guild card number set to %" PRId64, + ses->remote_guild_card_number); string message = string_printf( "The remote server\nhas assigned your\nGuild Card number:\n\tC6%" PRId64, - session.remote_guild_card_number); - send_ship_info(session.client_channel, decode_sjis(message)); + ses->remote_guild_card_number); + send_ship_info(ses->client_channel, decode_sjis(message)); } - if (session.license) { - cmd.guild_card_number = session.license->serial_number; + if (ses->license) { + cmd.guild_card_number = ses->license->serial_number; } // It seems the client ignores the length of the 04 command, and always copies @@ -524,32 +516,31 @@ static HandlerResult S_V123_04(shared_ptr, // the copyright string from the server init command). We simulate that here. // If there was previously a guild card number, assume we got the lobby server // init text instead of the port map init text. - memcpy(session.remote_client_config_data.data(), + memcpy(ses->remote_client_config_data.data(), had_guild_card_number ? "t Lobby Server. Copyright SEGA E" : "t Port Map. Copyright SEGA Enter", - session.remote_client_config_data.bytes()); - memcpy(session.remote_client_config_data.data(), &cmd.cfg, + 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), - session.remote_client_config_data.bytes())); + ses->remote_client_config_data.bytes())); // If the guild card number was not set, pretend (to the server) that this is // the first 04 command the client has received. The client responds with a 96 // (checksum) in that case. if (!had_guild_card_number) { le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; - session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); + ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); } - return session.license ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; + return ses->license ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult S_V123_06(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.license) { +static HandlerResult S_V123_06(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->license) { auto& cmd = check_size_t(data, 0xFFFF); - if (cmd.guild_card_number == session.remote_guild_card_number) { - cmd.guild_card_number = session.license->serial_number; + if (cmd.guild_card_number == ses->remote_guild_card_number) { + cmd.guild_card_number = ses->license->serial_number; return HandlerResult::Type::MODIFIED; } } @@ -557,17 +548,16 @@ static HandlerResult S_V123_06(shared_ptr, } template -static HandlerResult S_41(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_41(shared_ptr ses, uint16_t, uint32_t, string& data) { bool modified = false; - if (session.license) { + if (ses->license) { auto& cmd = check_size_t(data); - if (cmd.searcher_guild_card_number == session.remote_guild_card_number) { - cmd.searcher_guild_card_number = session.license->serial_number; + if (cmd.searcher_guild_card_number == ses->remote_guild_card_number) { + cmd.searcher_guild_card_number = ses->license->serial_number; modified = true; } - if (cmd.result_guild_card_number == session.remote_guild_card_number) { - cmd.result_guild_card_number = session.license->serial_number; + if (cmd.result_guild_card_number == ses->remote_guild_card_number) { + cmd.result_guild_card_number = ses->license->serial_number; modified = true; } } @@ -579,17 +569,16 @@ constexpr on_command_t S_P_41 = &S_41; constexpr on_command_t S_B_41 = &S_41; template -static HandlerResult S_81(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_81(shared_ptr ses, uint16_t, uint32_t, string& data) { bool modified = false; - if (session.license) { + if (ses->license) { auto& cmd = check_size_t(data); - if (cmd.from_guild_card_number == session.remote_guild_card_number) { - cmd.from_guild_card_number = session.license->serial_number; + if (cmd.from_guild_card_number == ses->remote_guild_card_number) { + cmd.from_guild_card_number = ses->license->serial_number; modified = true; } - if (cmd.to_guild_card_number == session.remote_guild_card_number) { - cmd.to_guild_card_number = session.license->serial_number; + if (cmd.to_guild_card_number == ses->remote_guild_card_number) { + cmd.to_guild_card_number = ses->license->serial_number; modified = true; } } @@ -600,16 +589,15 @@ constexpr on_command_t S_DGX_81 = &S_81; constexpr on_command_t S_P_81 = &S_81; constexpr on_command_t S_B_81 = &S_81; -static HandlerResult S_88(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) { +static HandlerResult S_88(shared_ptr ses, uint16_t, uint32_t flag, string& data) { bool modified = false; - if (session.license) { + if (ses->license) { size_t expected_size = sizeof(S_ArrowUpdateEntry_88) * flag; auto* entries = &check_size_t( data, expected_size, expected_size); for (size_t x = 0; x < flag; x++) { - if (entries[x].guild_card_number == session.remote_guild_card_number) { - entries[x].guild_card_number = session.license->serial_number; + if (entries[x].guild_card_number == ses->remote_guild_card_number) { + entries[x].guild_card_number = ses->license->serial_number; modified = true; } } @@ -617,25 +605,23 @@ static HandlerResult S_88(shared_ptr, return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult S_B1(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { +static HandlerResult S_B1(shared_ptr ses, uint16_t, uint32_t, string&) { // Block all time updates from the remote server, so client's time remains // consistent - session.server_channel.send(0x99, 0x00); + ses->server_channel.send(0x99, 0x00); return HandlerResult::Type::SUPPRESS; } -static HandlerResult S_B2(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) { +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 && session.options.save_files) { + if (cmd.code_size && ses->options.save_files) { uint64_t filename_timestamp = now(); string code = data.substr(sizeof(S_ExecuteCode_B2)); - if (session.newserv_client_config.cfg.flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) { + if (ses->newserv_client_config.cfg.flags & Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL) { StringReader r(code); - bool is_big_endian = (session.version == GameVersion::GC || session.version == GameVersion::DC); + 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(); uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l(); @@ -668,10 +654,10 @@ static HandlerResult S_B2(shared_ptr, string output_filename = string_printf("code.%" PRId64 ".bin", filename_timestamp); save_file(output_filename, data); - session.log.info("Wrote code from server to file %s", output_filename.c_str()); + ses->log.info("Wrote code from server to file %s", output_filename.c_str()); #ifdef HAVE_RESOURCE_FILE - if (session.version == GameVersion::GC) { + if (ses->version == GameVersion::GC) { try { if (code.size() < sizeof(S_ExecuteCode_Footer_GC_B2)) { throw runtime_error("code section is too small"); @@ -707,38 +693,37 @@ static HandlerResult S_B2(shared_ptr, fprintf(f.get(), "// checksum_size = 0x%" PRIX32 "\n", cmd.checksum_size.load()); fwritex(f.get(), disassembly); } - session.log.info("Wrote disassembly to file %s", output_filename.c_str()); + ses->log.info("Wrote disassembly to file %s", output_filename.c_str()); } catch (const exception& e) { - session.log.info("Failed to disassemble code from server: %s", e.what()); + ses->log.info("Failed to disassemble code from server: %s", e.what()); } } #endif } - if (session.options.function_call_return_value >= 0) { - session.log.info("Blocking function call from server"); + if (ses->options.function_call_return_value >= 0) { + ses->log.info("Blocking function call from server"); C_ExecuteCodeResult_B3 cmd; - cmd.return_value = session.options.function_call_return_value; + cmd.return_value = ses->options.function_call_return_value; cmd.checksum = 0; - session.server_channel.send(0xB3, flag, &cmd, sizeof(cmd)); + ses->server_channel.send(0xB3, flag, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } else { - session.function_call_return_handler_queue.emplace_back(nullptr); + ses->function_call_return_handler_queue.emplace_back(nullptr); return HandlerResult::Type::FORWARD; } } -static HandlerResult C_B3(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult C_B3(shared_ptr ses, uint16_t, uint32_t, string& data) { auto cmd = check_size_t(data); - if (session.function_call_return_handler_queue.empty()) { - session.log.warning("Received function call result with empty result queue"); + if (ses->function_call_return_handler_queue.empty()) { + ses->log.warning("Received function call result with empty result queue"); return HandlerResult::Type::FORWARD; } - auto handler = std::move(session.function_call_return_handler_queue.front()); - session.function_call_return_handler_queue.pop_front(); + auto handler = std::move(ses->function_call_return_handler_queue.front()); + ses->function_call_return_handler_queue.pop_front(); if (handler != nullptr) { handler(cmd.return_value, cmd.checksum); return HandlerResult::Type::SUPPRESS; @@ -747,28 +732,26 @@ static HandlerResult C_B3(shared_ptr, } } -static HandlerResult S_B_E7(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.options.save_files) { +static HandlerResult S_B_E7(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->options.save_files) { string output_filename = string_printf("player.%" PRId64 ".bin", now()); save_file(output_filename, data); - session.log.info("Wrote player data to file %s", output_filename.c_str()); + ses->log.info("Wrote player data to file %s", output_filename.c_str()); } return HandlerResult::Type::FORWARD; } template -static HandlerResult S_C4(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) { +static HandlerResult S_C4(shared_ptr ses, uint16_t, uint32_t flag, string& data) { bool modified = false; - if (session.license) { + if (ses->license) { size_t expected_size = sizeof(CmdT) * flag; // Some servers (e.g. Schtserv) send extra data on the end of this command; // the client ignores it so we can ignore it too auto* entries = &check_size_t(data, expected_size, 0xFFFF); for (size_t x = 0; x < flag; x++) { - if (entries[x].guild_card_number == session.remote_guild_card_number) { - entries[x].guild_card_number = session.license->serial_number; + if (entries[x].guild_card_number == ses->remote_guild_card_number) { + entries[x].guild_card_number = ses->license->serial_number; modified = true; } } @@ -778,21 +761,19 @@ static HandlerResult S_C4(shared_ptr, constexpr on_command_t S_V3_C4 = &S_C4; -static HandlerResult S_G_E4(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_G_E4(shared_ptr ses, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); bool modified = false; for (size_t x = 0; x < 4; x++) { - if (cmd.entries[x].guild_card_number == session.remote_guild_card_number) { - cmd.entries[x].guild_card_number = session.license->serial_number; + if (cmd.entries[x].guild_card_number == ses->remote_guild_card_number) { + cmd.entries[x].guild_card_number = ses->license->serial_number; modified = true; } } return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult S_B_22(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_B_22(shared_ptr ses, uint16_t, uint32_t, string& data) { // We use this command (which is sent before the init encryption command) to // detect a particular server behavior that we'll have to work around later. // It looks like this command's existence is another anti-proxy measure, since @@ -804,14 +785,13 @@ static HandlerResult S_B_22(shared_ptr, // the story behind why they put that string there. if ((data.size() == 0x2C) && (fnv1a64(data.data(), data.size()) == 0x8AF8314316A27994)) { - session.log.info("Enabling remote IP CRC patch"); - session.enable_remote_ip_crc_patch = true; + ses->log.info("Enabling remote IP CRC patch"); + ses->enable_remote_ip_crc_patch = true; } return HandlerResult::Type::FORWARD; } -static HandlerResult S_19_P_14(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_19_P_14(shared_ptr ses, uint16_t, uint32_t, string& data) { // If the command is shorter than 6 bytes, use the previous server command to // fill it in. This simulates a behavior used by some private servers where a // longer previous command is used to fill part of the client's receive buffer @@ -819,25 +799,25 @@ static HandlerResult S_19_P_14(shared_ptr, // which results in the client using the previous command's data as part of // the 19 command's contents. They presumably do this in an attempt to prevent // people from using proxies. - if (data.size() < sizeof(session.prev_server_command_bytes)) { + if (data.size() < sizeof(ses->prev_server_command_bytes)) { data.append( - reinterpret_cast(&session.prev_server_command_bytes[data.size()]), - sizeof(session.prev_server_command_bytes) - data.size()); + reinterpret_cast(&ses->prev_server_command_bytes[data.size()]), + sizeof(ses->prev_server_command_bytes) - data.size()); } if (data.size() < sizeof(S_Reconnect_19)) { data.resize(sizeof(S_Reconnect_19), '\0'); } - if (session.enable_remote_ip_crc_patch) { - session.remote_ip_crc = crc32(data.data(), 4); + if (ses->enable_remote_ip_crc_patch) { + ses->remote_ip_crc = crc32(data.data(), 4); } // Set the destination netloc appropriately - memset(&session.next_destination, 0, sizeof(session.next_destination)); + memset(&ses->next_destination, 0, sizeof(ses->next_destination)); struct sockaddr_in* sin = reinterpret_cast( - &session.next_destination); + &ses->next_destination); sin->sin_family = AF_INET; - if (session.version == GameVersion::PATCH) { + if (ses->version == GameVersion::PATCH) { auto& cmd = check_size_t(data); sin->sin_addr.s_addr = cmd.address.load_raw(); // Already big-endian sin->sin_port = htons(cmd.port); @@ -849,24 +829,24 @@ static HandlerResult S_19_P_14(shared_ptr, sin->sin_port = htons(cmd.port); } - if (!session.client_channel.connected()) { - session.log.warning("Received reconnect command with no destination present"); + if (!ses->client_channel.connected()) { + ses->log.warning("Received reconnect command with no destination present"); return HandlerResult::Type::SUPPRESS; - } else if (session.version != GameVersion::BB) { + } else if (ses->version != GameVersion::BB) { // Hide redirects from the client completely. The new destination server // will presumably send a new encryption init command, which the handlers // will appropriately respond to. - session.server_channel.crypt_in.reset(); - session.server_channel.crypt_out.reset(); + ses->server_channel.crypt_in.reset(); + ses->server_channel.crypt_out.reset(); // We already modified next_destination, so start the connection process - session.connect(); + ses->connect(); return HandlerResult::Type::SUPPRESS; } else { const struct sockaddr_in* sin = reinterpret_cast( - &session.client_channel.local_addr); + &ses->client_channel.local_addr); if (sin->sin_family != AF_INET) { throw logic_error("existing connection is not ipv4"); } @@ -877,36 +857,33 @@ static HandlerResult S_19_P_14(shared_ptr, } } -static HandlerResult S_V3_1A_D5(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { +static HandlerResult S_V3_1A_D5(shared_ptr ses, uint16_t, uint32_t, string&) { // If the client is a version that sends close confirmations and the client // has the no-close-confirmation flag set in its newserv client config, send a // fake confirmation to the remote server immediately. - if (((session.version == GameVersion::GC) || (session.version == GameVersion::XB)) && - (session.newserv_client_config.cfg.flags & Client::Flag::NO_D6)) { - session.server_channel.send(0xD6); + if (((ses->version == GameVersion::GC) || (ses->version == GameVersion::XB)) && + (ses->newserv_client_config.cfg.flags & Client::Flag::NO_D6)) { + ses->server_channel.send(0xD6); } return HandlerResult::Type::FORWARD; } -static HandlerResult S_V3_BB_DA(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string&) { +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 ((session.version == GameVersion::GC) && - (session.newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) { + if ((ses->version == GameVersion::GC) && + (ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) { return HandlerResult::Type::SUPPRESS; - } else if ((session.options.override_lobby_event >= 0) && - (static_cast(flag) != session.options.override_lobby_event)) { - return HandlerResult(HandlerResult::Type::MODIFIED, 0xDA, session.options.override_lobby_event); + } 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 { return HandlerResult::Type::FORWARD; } } -static HandlerResult S_6x(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.options.save_files) { - if ((session.version == GameVersion::GC) && (data.size() >= 0x14)) { +static HandlerResult S_6x(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->options.save_files) { + if ((ses->version == GameVersion::GC) && (data.size() >= 0x14)) { if (static_cast(data[0]) == 0xB6) { const auto& header = check_size_t(data, 0xFFFF); if (header.subsubcommand == 0x00000041) { @@ -917,10 +894,10 @@ static HandlerResult S_6x(shared_ptr, data.data() + sizeof(cmd), data.size() - sizeof(cmd)); save_file(filename, map_data); if (map_data.size() != sizeof(Episode3::MapDefinition) && map_data.size() != sizeof(Episode3::MapDefinitionTrial)) { - session.log.warning("Wrote %zu bytes to %s (expected %zu or %zu bytes; the file may be invalid)", + ses->log.warning("Wrote %zu bytes to %s (expected %zu or %zu bytes; the file may be invalid)", map_data.size(), filename.c_str(), sizeof(Episode3::MapDefinitionTrial), sizeof(Episode3::MapDefinition)); } else { - session.log.info("Wrote %zu bytes to %s", map_data.size(), filename.c_str()); + ses->log.info("Wrote %zu bytes to %s", map_data.size(), filename.c_str()); } } } @@ -930,7 +907,7 @@ static HandlerResult S_6x(shared_ptr, bool modified = false; if (!data.empty()) { // Unmask any masked Episode 3 commands from the server - if ((session.version == GameVersion::GC) && (data.size() > 8) && + if ((ses->version == GameVersion::GC) && (data.size() > 8) && ((static_cast(data[0]) == 0xB3) || (static_cast(data[0]) == 0xB4) || (static_cast(data[0]) == 0xB5))) { @@ -947,7 +924,7 @@ static HandlerResult S_6x(shared_ptr, sizeof(G_AttackFinished_6x46)); size_t allowed_count = min(cmd.header.size - 2, 11); if (cmd.count > allowed_count) { - session.log.warning("Blocking subcommand 6x46 with invalid count"); + ses->log.warning("Blocking subcommand 6x46 with invalid count"); return HandlerResult::Type::SUPPRESS; } } else if (data[0] == 0x47) { @@ -956,7 +933,7 @@ static HandlerResult S_6x(shared_ptr, sizeof(G_CastTechnique_6x47)); size_t allowed_count = min(cmd.header.size - 2, 10); if (cmd.target_count > allowed_count) { - session.log.warning("Blocking subcommand 6x47 with invalid count"); + ses->log.warning("Blocking subcommand 6x47 with invalid count"); return HandlerResult::Type::SUPPRESS; } } else if (data[0] == 0x49) { @@ -965,21 +942,21 @@ static HandlerResult S_6x(shared_ptr, sizeof(G_SubtractPBEnergy_6x49)); size_t allowed_count = min(cmd.header.size - 3, 14); if (cmd.entry_count > allowed_count) { - session.log.warning("Blocking subcommand 6x49 with invalid count"); + ses->log.warning("Blocking subcommand 6x49 with invalid count"); return HandlerResult::Type::SUPPRESS; } } else if ((data[0] == 0x60) && - session.next_drop_item.data.data1d[0] && - (session.version != GameVersion::BB)) { + ses->next_drop_item.data.data1d[0] && + (ses->version != GameVersion::BB)) { const auto& cmd = check_size_t( data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60)); - session.next_drop_item.data.id = session.next_item_id++; - send_drop_item(session.server_channel, session.next_drop_item.data, + ses->next_drop_item.data.id = ses->next_item_id++; + send_drop_item(ses->server_channel, ses->next_drop_item.data, true, cmd.area, cmd.x, cmd.z, cmd.entity_id); - send_drop_item(session.client_channel, session.next_drop_item.data, + send_drop_item(ses->client_channel, ses->next_drop_item.data, true, cmd.area, cmd.x, cmd.z, cmd.entity_id); - session.next_drop_item.clear(); + ses->next_drop_item.clear(); return HandlerResult::Type::SUPPRESS; // Note: This static_cast is required to make compilers not complain that @@ -987,25 +964,25 @@ static HandlerResult S_6x(shared_ptr, // if we use -0x5E... apparently char is unsigned on some systems, or // std::string's char_type isn't char??) } else if ((static_cast(data[0]) == 0xA2) && - session.next_drop_item.data.data1d[0] && - (session.version != GameVersion::BB)) { + ses->next_drop_item.data.data1d[0] && + (ses->version != GameVersion::BB)) { const auto& cmd = check_size_t(data); - session.next_drop_item.data.id = session.next_item_id++; - send_drop_item(session.server_channel, session.next_drop_item.data, + ses->next_drop_item.data.id = ses->next_item_id++; + send_drop_item(ses->server_channel, ses->next_drop_item.data, false, cmd.area, cmd.x, cmd.z, cmd.entity_id); - send_drop_item(session.client_channel, session.next_drop_item.data, + send_drop_item(ses->client_channel, ses->next_drop_item.data, false, cmd.area, cmd.x, cmd.z, cmd.entity_id); - session.next_drop_item.clear(); + ses->next_drop_item.clear(); return HandlerResult::Type::SUPPRESS; } else if ((static_cast(data[0]) == 0xB5) && - (session.version == GameVersion::GC) && + (ses->version == GameVersion::GC) && (data.size() > 4)) { if (data[4] == 0x1A) { return HandlerResult::Type::SUPPRESS; } else if (data[4] == 0x36) { const auto& cmd = check_size_t(data); - if (session.is_in_game && (cmd.unknown_a1 >= 4)) { + if (ses->is_in_game && (cmd.unknown_a1 >= 4)) { return HandlerResult::Type::SUPPRESS; } } @@ -1015,32 +992,31 @@ static HandlerResult S_6x(shared_ptr, return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult C_GXB_61(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) { +static HandlerResult C_GXB_61(shared_ptr ses, uint16_t, uint32_t flag, string& data) { bool modified = false; // TODO: We should check if the info board text was actually modified and // return MODIFIED if so. - if (session.version == GameVersion::BB) { + if (ses->version == GameVersion::BB) { auto& pd = check_size_t(data, 0xFFFF); - if (session.options.enable_chat_filter) { + if (ses->options.enable_chat_filter) { add_color_inplace(pd.info_board.data(), pd.info_board.size()); } - if (session.options.blank_name) { + if (ses->options.blank_name) { pd.disp.name = " "; modified = true; } - if (session.options.red_name && pd.disp.visual.name_color != 0xFFFF0000) { + if (ses->options.red_name && pd.disp.visual.name_color != 0xFFFF0000) { pd.disp.visual.name_color = 0xFFFF0000; pd.records.challenge.title_color = 0x7C00; modified = true; - } else if (session.options.blank_name && pd.disp.visual.name_color != 0x00000000) { + } else if (ses->options.blank_name && pd.disp.visual.name_color != 0x00000000) { pd.disp.visual.name_color = 0x00000000; modified = true; } - if (!session.challenge_rank_title_override.empty()) { - pd.records.challenge.title_color = encode_xrgb1555(session.challenge_rank_color_override); - pd.records.challenge.rank_title = encrypt_challenge_rank_text(session.challenge_rank_title_override); + if (!ses->challenge_rank_title_override.empty()) { + pd.records.challenge.title_color = encode_xrgb1555(ses->challenge_rank_color_override); + pd.records.challenge.rank_title = encrypt_challenge_rank_text(ses->challenge_rank_title_override); } } else { @@ -1060,33 +1036,32 @@ static HandlerResult C_GXB_61(shared_ptr, } else { pd = &check_size_t(data, 0xFFFF); } - if (session.options.enable_chat_filter) { + if (ses->options.enable_chat_filter) { add_color_inplace(pd->info_board.data(), pd->info_board.size()); } - if (session.options.blank_name) { + if (ses->options.blank_name) { pd->disp.visual.name = " "; modified = true; } - if (session.options.red_name && pd->disp.visual.name_color != 0xFFFF0000) { + if (ses->options.red_name && pd->disp.visual.name_color != 0xFFFF0000) { pd->disp.visual.name_color = 0xFFFF0000; pd->records.challenge.stats.title_color = 0x7C00; modified = true; - } else if (session.options.blank_name && pd->disp.visual.name_color != 0x00000000) { + } else if (ses->options.blank_name && pd->disp.visual.name_color != 0x00000000) { pd->disp.visual.name_color = 0x00000000; modified = true; } - if (!session.challenge_rank_title_override.empty()) { - pd->records.challenge.stats.title_color = encode_xrgb1555(session.challenge_rank_color_override); - pd->records.challenge.rank_title = encrypt_challenge_rank_text(session.challenge_rank_title_override); + if (!ses->challenge_rank_title_override.empty()) { + pd->records.challenge.stats.title_color = encode_xrgb1555(ses->challenge_rank_color_override); + pd->records.challenge.rank_title = encrypt_challenge_rank_text(ses->challenge_rank_title_override); } } return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult C_GX_D9(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.options.enable_chat_filter) { +static HandlerResult C_GX_D9(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->options.enable_chat_filter) { add_color_inplace(data.data(), data.size()); // TODO: We should check if the info board text was actually modified and // return MODIFIED if so. @@ -1094,9 +1069,8 @@ static HandlerResult C_GX_D9(shared_ptr, return HandlerResult::Type::FORWARD; } -static HandlerResult C_B_D9(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.options.enable_chat_filter) { +static HandlerResult C_B_D9(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->options.enable_chat_filter) { char16_t* text = reinterpret_cast(data.data()); add_color_inplace(text, data.size() / sizeof(char16_t)); // TODO: We should check if the info board text was actually modified and @@ -1106,20 +1080,19 @@ static HandlerResult C_B_D9(shared_ptr, } template -static HandlerResult S_44_A6(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) { +static HandlerResult S_44_A6(shared_ptr ses, uint16_t command, uint32_t, string& data) { const auto& cmd = check_size_t(data); string filename = cmd.filename; string output_filename; bool is_download = (command == 0xA6); - if (session.options.save_files) { + if (ses->options.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" && (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + if (extension == ".bin" && (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { extension += ".mnm"; } } else { @@ -1142,14 +1115,14 @@ static HandlerResult S_44_A6(shared_ptr, } // Episode 3 download quests aren't DLQ-encoded - bool decode_dlq = is_download && !(session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3); + bool decode_dlq = is_download && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3); ProxyServer::LinkedSession::SavingFile sf( cmd.filename, output_filename, cmd.file_size, decode_dlq); - session.saving_files.emplace(cmd.filename, std::move(sf)); - if (session.options.save_files) { - session.log.info("Saving %s from server to %s", filename.c_str(), output_filename.c_str()); + ses->saving_files.emplace(cmd.filename, std::move(sf)); + if (ses->options.save_files) { + ses->log.info("Saving %s from server to %s", filename.c_str(), output_filename.c_str()); } else { - session.log.info("Tracking file %s", filename.c_str()); + ses->log.info("Tracking file %s", filename.c_str()); } return HandlerResult::Type::FORWARD; @@ -1160,50 +1133,48 @@ constexpr on_command_t S_PG_44_A6 = &S_44_A6; constexpr on_command_t S_X_44_A6 = &S_44_A6; constexpr on_command_t S_B_44_A6 = &S_44_A6; -static HandlerResult S_13_A7(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_13_A7(shared_ptr ses, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); bool modified = false; ProxyServer::LinkedSession::SavingFile* sf = nullptr; try { - sf = &session.saving_files.at(cmd.filename); + sf = &ses->saving_files.at(cmd.filename); } catch (const out_of_range&) { string filename = cmd.filename; - session.log.warning("Received data for non-open file %s", filename.c_str()); + ses->log.warning("Received data for non-open file %s", filename.c_str()); return HandlerResult::Type::FORWARD; } if (cmd.data_size > sf->remaining_bytes) { - session.log.warning("Chunk size extends beyond original file size; truncating file"); + ses->log.warning("Chunk size extends beyond original file size; truncating file"); cmd.data_size = sf->remaining_bytes; modified = true; } else if (cmd.data_size > 0x400) { - session.log.warning("Chunk data size is invalid; truncating to 0x400"); + ses->log.warning("Chunk data size is invalid; truncating to 0x400"); cmd.data_size = 0x400; modified = true; } if (!sf->output_filename.empty()) { - session.log.info("Adding %" PRIu32 " bytes to %s => %s", + ses->log.info("Adding %" PRIu32 " bytes to %s => %s", cmd.data_size.load(), sf->basename.c_str(), sf->output_filename.c_str()); sf->blocks.emplace_back(reinterpret_cast(cmd.data.data()), cmd.data_size); } sf->remaining_bytes -= cmd.data_size; if (sf->remaining_bytes == 0) { - session.log.info("Writing file %s => %s", sf->basename.c_str(), sf->output_filename.c_str()); + ses->log.info("Writing file %s => %s", sf->basename.c_str(), sf->output_filename.c_str()); sf->write(); - session.saving_files.erase(cmd.filename); + ses->saving_files.erase(cmd.filename); } return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult S_G_B7(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { - if (session.options.ep3_infinite_meseta) { +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) { auto& cmd = check_size_t(data); if (cmd.meseta != 1000000) { cmd.meseta = 1000000; @@ -1212,44 +1183,42 @@ static HandlerResult S_G_B7(shared_ptr, } return HandlerResult::Type::FORWARD; } else { - session.server_channel.send(0xB7, 0x00); + ses->server_channel.send(0xB7, 0x00); return HandlerResult::Type::SUPPRESS; } } -static HandlerResult S_G_B8(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.options.save_files) { +static HandlerResult S_G_B8(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->options.save_files) { if (data.size() < 4) { - session.log.warning("Card list data size is too small; not saving file"); + ses->log.warning("Card list data size is too small; not saving file"); return HandlerResult::Type::FORWARD; } StringReader r(data); size_t size = r.get_u32l(); if (r.remaining() < size) { - session.log.warning("Card list data size extends beyond end of command; not saving file"); + ses->log.warning("Card list data size extends beyond end of command; not saving file"); return HandlerResult::Type::FORWARD; } string output_filename = string_printf("card-definitions.%" PRIu64 ".mnr", now()); save_file(output_filename, r.read(size)); - session.log.info("Wrote %zu bytes to %s", size, output_filename.c_str()); + ses->log.info("Wrote %zu bytes to %s", size, output_filename.c_str()); } // 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. - session.newserv_client_config.cfg.flags &= ~Client::Flag::HAS_EP3_CARD_DEFS; + ses->newserv_client_config.cfg.flags &= ~Client::Flag::HAS_EP3_CARD_DEFS; - return (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) + return (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; } -static HandlerResult S_G_B9(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_G_B9(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (session.options.save_files) { + if (ses->options.save_files) { try { const auto& header = check_size_t(data, 0xFFFF); @@ -1269,22 +1238,21 @@ static HandlerResult S_G_B9(shared_ptr, output_filename += ".bin"; } save_file(output_filename, decompressed_data); - session.log.info("Wrote %zu bytes to %s", + ses->log.info("Wrote %zu bytes to %s", decompressed_data.size(), output_filename.c_str()); } catch (const exception& e) { - session.log.warning("Failed to save file: %s", e.what()); + ses->log.warning("Failed to save file: %s", e.what()); } } - return (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) + return (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; } -static HandlerResult S_G_EF(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) { - if (session.options.ep3_infinite_meseta) { +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) { auto& cmd = check_size_t(data, offsetof(S_StartCardAuction_GC_Ep3_EF, unused), 0xFFFF); if (cmd.points_available != 0x7FFF) { @@ -1298,14 +1266,12 @@ static HandlerResult S_G_EF(shared_ptr, } } -static HandlerResult S_B_EF(shared_ptr, - ProxyServer::LinkedSession&, uint16_t, uint32_t, string&) { +static HandlerResult S_B_EF(shared_ptr, uint16_t, uint32_t, string&) { return HandlerResult::Type::SUPPRESS; } -static HandlerResult S_G_BA(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - if (session.options.ep3_infinite_meseta) { +static HandlerResult S_G_BA(shared_ptr ses, uint16_t, uint32_t, string& data) { + if (ses->options.ep3_infinite_meseta) { auto& cmd = check_size_t(data); if (cmd.remaining_meseta != 1000000) { cmd.remaining_meseta = 1000000; @@ -1315,32 +1281,31 @@ static HandlerResult S_G_BA(shared_ptr, return HandlerResult::Type::FORWARD; } -static void update_leader_id(ProxyServer::LinkedSession& session, uint8_t leader_id) { - if (session.leader_client_id != leader_id) { - session.leader_client_id = leader_id; - session.log.info("Changed room leader to %zu", session.leader_client_id); - if (session.leader_client_id == session.lobby_client_id) { - send_text_message(session.client_channel, u"$C6You are now the leader"); +static void update_leader_id(shared_ptr ses, uint8_t leader_id) { + 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->leader_client_id == ses->lobby_client_id) { + send_text_message(ses->client_channel, u"$C6You are now the leader"); } } } template -static HandlerResult S_65_67_68_EB(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) { +static HandlerResult S_65_67_68_EB(shared_ptr ses, uint16_t command, uint32_t flag, string& data) { if (command == 0x67) { - session.clear_lobby_players(12); - session.is_in_game = false; - session.is_in_quest = false; - session.area = 0x0F; + ses->clear_lobby_players(12); + ses->is_in_game = false; + ses->is_in_quest = false; + ses->area = 0x0F; // This command can cause the client to no longer send D6 responses when // 1A/D5 large message boxes are closed. newserv keeps track of this // 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 (session.newserv_client_config.cfg.flags & Client::Flag::NO_D6_AFTER_LOBBY) { - session.newserv_client_config.cfg.flags |= Client::Flag::NO_D6; + if (ses->newserv_client_config.cfg.flags & Client::Flag::NO_D6_AFTER_LOBBY) { + ses->newserv_client_config.cfg.flags |= Client::Flag::NO_D6; } } @@ -1349,42 +1314,42 @@ static HandlerResult S_65_67_68_EB(shared_ptr, bool modified = false; size_t num_replacements = 0; - session.lobby_client_id = cmd.lobby_flags.client_id; - update_leader_id(session, cmd.lobby_flags.leader_id); + ses->lobby_client_id = cmd.lobby_flags.client_id; + update_leader_id(ses, cmd.lobby_flags.leader_id); for (size_t x = 0; x < flag; x++) { size_t index = cmd.entries[x].lobby_data.client_id; - if (index >= session.lobby_players.size()) { - session.log.warning("Ignoring invalid player index %zu at position %zu", index, x); + if (index >= ses->lobby_players.size()) { + ses->log.warning("Ignoring invalid player index %zu at position %zu", index, x); } else { string name = encode_sjis(cmd.entries[x].disp.visual.name); - if (session.license && (cmd.entries[x].lobby_data.guild_card == session.remote_guild_card_number)) { - cmd.entries[x].lobby_data.guild_card = session.license->serial_number; + if (ses->license && (cmd.entries[x].lobby_data.guild_card == ses->remote_guild_card_number)) { + cmd.entries[x].lobby_data.guild_card = ses->license->serial_number; num_replacements++; modified = true; - } else if (session.options.enable_player_notifications && command != 0x67) { - send_text_message_printf(session.client_channel, "$C6Join: %zu/%" PRIu32 "\n%s", + } else if (ses->options.enable_player_notifications && command != 0x67) { + send_text_message_printf(ses->client_channel, "$C6Join: %zu/%" PRIu32 "\n%s", index, cmd.entries[x].lobby_data.guild_card.load(), name.c_str()); } - auto& p = session.lobby_players[index]; + auto& p = ses->lobby_players[index]; p.guild_card_number = cmd.entries[x].lobby_data.guild_card; p.name = name; p.section_id = cmd.entries[x].disp.visual.section_id; p.char_class = cmd.entries[x].disp.visual.char_class; - session.log.info("Added lobby player: (%zu) %" PRIu32 " %s", + ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", index, p.guild_card_number, p.name.c_str()); } } if (num_replacements > 1) { - session.log.warning("Proxied player appears multiple times in lobby"); + ses->log.warning("Proxied player appears multiple times in lobby"); } - if (session.options.override_lobby_event >= 0) { - cmd.lobby_flags.event = session.options.override_lobby_event; + if (ses->options.override_lobby_event >= 0) { + cmd.lobby_flags.event = ses->options.override_lobby_event; modified = true; } - if (session.options.override_lobby_number >= 0) { - cmd.lobby_flags.lobby_number = session.options.override_lobby_number; + if (ses->options.override_lobby_number >= 0) { + cmd.lobby_flags.lobby_number = ses->options.override_lobby_number; modified = true; } @@ -1397,32 +1362,31 @@ constexpr on_command_t S_X_65_67_68 = &S_65_67_68_EB; constexpr on_command_t S_B_65_67_68 = &S_65_67_68_EB; template -static HandlerResult S_64(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) { +static HandlerResult S_64(shared_ptr ses, uint16_t, uint32_t flag, string& data) { CmdT* cmd; S_JoinGame_GC_Ep3_64* cmd_ep3 = nullptr; - if (session.sub_version >= 0x40) { + if (ses->sub_version >= 0x40) { cmd = &check_size_t(data, sizeof(S_JoinGame_GC_Ep3_64)); cmd_ep3 = &check_size_t(data); } else { cmd = &check_size_t(data); } - session.clear_lobby_players(4); - session.area = 0; - session.is_in_game = true; - session.is_in_quest = false; + ses->clear_lobby_players(4); + ses->area = 0; + ses->is_in_game = true; + ses->is_in_quest = false; bool modified = false; - session.lobby_client_id = cmd->client_id; - update_leader_id(session, cmd->leader_id); + 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 == session.remote_guild_card_number) { - cmd->lobby_data[x].guild_card = session.license->serial_number; + if (cmd->lobby_data[x].guild_card == ses->remote_guild_card_number) { + cmd->lobby_data[x].guild_card = ses->license->serial_number; modified = true; } - auto& p = session.lobby_players[x]; + auto& p = ses->lobby_players[x]; p.guild_card_number = cmd->lobby_data[x].guild_card; if (cmd_ep3) { ptext name = cmd_ep3->players_ep3[x].disp.visual.name; @@ -1432,20 +1396,20 @@ static HandlerResult S_64(shared_ptr, } else { p.name.clear(); } - session.log.info("Added lobby player: (%zu) %" PRIu32 " %s", + ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", x, p.guild_card_number, p.name.c_str()); } - if (session.options.override_section_id >= 0) { - cmd->section_id = session.options.override_section_id; + if (ses->options.override_section_id >= 0) { + cmd->section_id = ses->options.override_section_id; modified = true; } - if (session.options.override_lobby_event >= 0) { - cmd->event = session.options.override_lobby_event; + if (ses->options.override_lobby_event >= 0) { + cmd->event = ses->options.override_lobby_event; modified = true; } - if (session.options.override_random_seed >= 0) { - cmd->rare_seed = session.options.override_random_seed; + if (ses->options.override_random_seed >= 0) { + cmd->rare_seed = ses->options.override_random_seed; modified = true; } @@ -1457,115 +1421,110 @@ constexpr on_command_t S_P_64 = &S_64; constexpr on_command_t S_X_64 = &S_64; constexpr on_command_t S_B_64 = &S_64; -static HandlerResult S_E8(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_E8(shared_ptr ses, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); - session.clear_lobby_players(12); - session.area = 0; - session.is_in_game = true; - session.is_in_quest = false; + ses->clear_lobby_players(12); + ses->area = 0; + ses->is_in_game = true; + ses->is_in_quest = false; bool modified = false; - session.lobby_client_id = cmd.client_id; - update_leader_id(session, cmd.leader_id); + ses->lobby_client_id = cmd.client_id; + update_leader_id(ses, cmd.leader_id); for (size_t x = 0; x < 12; x++) { 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 == session.remote_guild_card_number) { - player_entry.lobby_data.guild_card = session.license->serial_number; + if (player_entry.lobby_data.guild_card == ses->remote_guild_card_number) { + player_entry.lobby_data.guild_card = ses->license->serial_number; modified = true; } - if (spec_entry.guild_card_number == session.remote_guild_card_number) { - spec_entry.guild_card_number = session.license->serial_number; + if (spec_entry.guild_card_number == ses->remote_guild_card_number) { + spec_entry.guild_card_number = ses->license->serial_number; modified = true; } - auto& p = session.lobby_players[x]; + auto& p = ses->lobby_players[x]; p.guild_card_number = player_entry.lobby_data.guild_card; ptext name = player_entry.disp.visual.name; p.name = name; p.section_id = player_entry.disp.visual.section_id; p.char_class = player_entry.disp.visual.char_class; - session.log.info("Added lobby player: (%zu) %" PRIu32 " %s", + ses->log.info("Added lobby player: (%zu) %" PRIu32 " %s", x, p.guild_card_number, p.name.c_str()); } - if (session.options.override_section_id >= 0) { - cmd.section_id = session.options.override_section_id; + if (ses->options.override_section_id >= 0) { + cmd.section_id = ses->options.override_section_id; modified = true; } - if (session.options.override_lobby_event >= 0) { - cmd.event = session.options.override_lobby_event; + if (ses->options.override_lobby_event >= 0) { + cmd.event = ses->options.override_lobby_event; modified = true; } - if (session.options.override_random_seed >= 0) { - cmd.rare_seed = session.options.override_random_seed; + if (ses->options.override_random_seed >= 0) { + cmd.rare_seed = ses->options.override_random_seed; modified = true; } return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } -static HandlerResult S_AC(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (!session.is_in_game) { +static HandlerResult S_AC(shared_ptr ses, uint16_t, uint32_t, string&) { + if (!ses->is_in_game) { return HandlerResult::Type::SUPPRESS; } else { - session.is_in_quest = true; + ses->is_in_quest = true; return HandlerResult::Type::FORWARD; } } -static HandlerResult S_66_69_E9(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult S_66_69_E9(shared_ptr ses, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); size_t index = cmd.client_id; - if (index >= session.lobby_players.size()) { - session.log.warning("Lobby leave command references missing position"); + if (index >= ses->lobby_players.size()) { + ses->log.warning("Lobby leave command references missing position"); } else { - auto& p = session.lobby_players[index]; - if (session.options.enable_player_notifications) { - send_text_message_printf(session.client_channel, "$C4Leave: %zu/%" PRIu32 "\n%s", + auto& p = ses->lobby_players[index]; + if (ses->options.enable_player_notifications) { + send_text_message_printf(ses->client_channel, "$C4Leave: %zu/%" PRIu32 "\n%s", index, p.guild_card_number, p.name.c_str()); } p.guild_card_number = 0; p.name.clear(); - session.log.info("Removed lobby player (%zu)", index); + ses->log.info("Removed lobby player (%zu)", index); } - update_leader_id(session, cmd.leader_id); + update_leader_id(ses, cmd.leader_id); return HandlerResult::Type::FORWARD; } -static HandlerResult C_98(shared_ptr s, - ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) { - session.area = 0x0F; - session.is_in_game = false; - session.is_in_quest = false; - if (session.version == GameVersion::GC || - session.version == GameVersion::XB || - session.version == GameVersion::BB) { - return C_GXB_61(s, session, command, flag, data); +static HandlerResult C_98(shared_ptr ses, uint16_t command, uint32_t flag, string& data) { + ses->area = 0x0F; + ses->is_in_game = false; + ses->is_in_quest = false; + if (ses->version == GameVersion::GC || + ses->version == GameVersion::XB || + ses->version == GameVersion::BB) { + return C_GXB_61(ses, command, flag, data); } else { return HandlerResult::Type::FORWARD; } } -static HandlerResult C_06(shared_ptr s, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult C_06(shared_ptr ses, uint16_t, uint32_t, string& data) { if (data.size() >= 12) { const auto& cmd = check_size_t(data, 0xFFFF); u16string text; uint8_t private_flags = 0; - if (session.version == GameVersion::PC || session.version == GameVersion::BB) { + if (ses->version == GameVersion::PC || ses->version == GameVersion::BB) { text = u16string(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t)); } else if ((cmd.text.dcv3[0] != '\t') && - (session.newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { + (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3)) { private_flags = cmd.text.dcv3[0]; text = decode_sjis(cmd.text.dcv3 + 1, data.size() - sizeof(C_Chat_06)); @@ -1581,19 +1540,19 @@ static HandlerResult C_06(shared_ptr s, bool is_command = (text[0] == '$') || (text[0] == '\t' && text[1] != 'C' && text[2] == '$'); - if (is_command && session.options.enable_chat_commands) { + if (is_command && ses->options.enable_chat_commands) { size_t offset = ((text[0] & 0xF0) == 0x40) ? 1 : 0; offset += (text[offset] == '$') ? 0 : 2; text = text.substr(offset); if (text.size() >= 2 && text[1] == '$') { - send_chat_message(session.server_channel, text.substr(1), private_flags); + send_chat_message(ses->server_channel, text.substr(1), private_flags); return HandlerResult::Type::SUPPRESS; } else { - on_chat_command(s, session, text); + on_chat_command(ses, text); return HandlerResult::Type::SUPPRESS; } - } else if (session.options.enable_chat_filter) { + } else if (ses->options.enable_chat_filter) { add_color_inplace(data.data() + 8, data.size() - 8); // TODO: We should return MODIFIED here if the message was changed by // the add_color_inplace call @@ -1608,17 +1567,16 @@ static HandlerResult C_06(shared_ptr s, } } -static HandlerResult C_40(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult C_40(shared_ptr ses, uint16_t, uint32_t, string& data) { bool modified = false; - if (session.license) { + if (ses->license) { auto& cmd = check_size_t(data); - if (cmd.searcher_guild_card_number == session.license->serial_number) { - cmd.searcher_guild_card_number = session.remote_guild_card_number; + if (cmd.searcher_guild_card_number == ses->license->serial_number) { + cmd.searcher_guild_card_number = ses->remote_guild_card_number; modified = true; } - if (cmd.target_guild_card_number == session.license->serial_number) { - cmd.target_guild_card_number = session.remote_guild_card_number; + if (cmd.target_guild_card_number == ses->license->serial_number) { + cmd.target_guild_card_number = ses->remote_guild_card_number; modified = true; } } @@ -1626,15 +1584,14 @@ static HandlerResult C_40(shared_ptr, } template -static HandlerResult C_81(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { +static HandlerResult C_81(shared_ptr ses, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); - if (session.license) { - if (cmd.from_guild_card_number == session.license->serial_number) { - cmd.from_guild_card_number = session.remote_guild_card_number; + if (ses->license) { + if (cmd.from_guild_card_number == ses->license->serial_number) { + cmd.from_guild_card_number = ses->remote_guild_card_number; } - if (cmd.to_guild_card_number == session.license->serial_number) { - cmd.to_guild_card_number = session.remote_guild_card_number; + if (cmd.to_guild_card_number == ses->license->serial_number) { + cmd.to_guild_card_number = ses->remote_guild_card_number; } } // GC clients send uninitialized memory here; don't forward it @@ -1647,22 +1604,21 @@ constexpr on_command_t C_P_81 = &C_81; constexpr on_command_t C_B_81 = &C_81; template -void C_6x_movement(ProxyServer::LinkedSession& session, const string& data) { +void C_6x_movement(shared_ptr ses, const string& data) { const auto& cmd = check_size_t(data); - session.x = cmd.x; - session.z = cmd.z; + ses->x = cmd.x; + ses->z = cmd.z; } template -static HandlerResult C_6x(shared_ptr s, - ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) { - if (session.license && !data.empty()) { +static HandlerResult C_6x(shared_ptr ses, uint16_t command, uint32_t flag, string& data) { + if (ses->license && !data.empty()) { // On BB, the 6x06 command is blank - the server generates the actual Guild // Card contents and sends it to the target client. - if (data[0] == 0x06 && session.version != GameVersion::BB) { + if (data[0] == 0x06 && ses->version != GameVersion::BB) { auto& cmd = check_size_t(data); - if (cmd.guild_card_number == session.license->serial_number) { - cmd.guild_card_number = session.remote_guild_card_number; + if (cmd.guild_card_number == ses->license->serial_number) { + cmd.guild_card_number = ses->remote_guild_card_number; } } } @@ -1670,33 +1626,33 @@ static HandlerResult C_6x(shared_ptr s, if (!data.empty()) { if (data[0] == 0x21) { const auto& cmd = check_size_t(data); - session.area = cmd.area; + ses->area = cmd.area; } else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) { - if (session.options.infinite_hp) { - send_player_stats_change(session.client_channel, - session.lobby_client_id, PlayerStatsChange::ADD_HP, 2550); - send_player_stats_change(session.server_channel, - session.lobby_client_id, PlayerStatsChange::ADD_HP, 2550); + if (ses->options.infinite_hp) { + send_player_stats_change(ses->client_channel, + ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550); + send_player_stats_change(ses->server_channel, + ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550); } } else if (data[0] == 0x3E) { - C_6x_movement(session, data); + C_6x_movement(ses, data); } else if (data[0] == 0x3F) { - C_6x_movement(session, data); + C_6x_movement(ses, data); } else if (data[0] == 0x40) { - C_6x_movement(session, data); + C_6x_movement(ses, data); } else if (data[0] == 0x42) { - C_6x_movement(session, data); + C_6x_movement(ses, data); } else if (data[0] == 0x48) { - if (session.options.infinite_tp) { - send_player_stats_change(session.client_channel, - session.lobby_client_id, PlayerStatsChange::ADD_TP, 255); - send_player_stats_change(session.server_channel, - session.lobby_client_id, PlayerStatsChange::ADD_TP, 255); + if (ses->options.infinite_tp) { + send_player_stats_change(ses->client_channel, + ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255); + send_player_stats_change(ses->server_channel, + ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255); } } } - return C_6x(s, session, command, flag, data); + return C_6x(ses, command, flag, data); } constexpr on_command_t C_D_6x = &C_6x; @@ -1705,37 +1661,35 @@ constexpr on_command_t C_GX_6x = &C_6x; constexpr on_command_t C_B_6x = &C_6x; template <> -HandlerResult C_6x(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { - check_implemented_subcommand(session, data); +HandlerResult C_6x(shared_ptr ses, uint16_t, uint32_t, string& data) { + check_implemented_subcommand(ses, data); - if (!data.empty() && (data[0] == 0x05) && session.options.switch_assist) { + if (!data.empty() && (data[0] == 0x05) && ses->options.switch_assist) { auto& cmd = check_size_t(data); if (cmd.flags && cmd.header.object_id != 0xFFFF) { - if (session.last_switch_enabled_command.header.subcommand == 0x05) { - session.log.info("Switch assist: replaying previous enable command"); - session.server_channel.send(0x60, 0x00, &session.last_switch_enabled_command, - sizeof(session.last_switch_enabled_command)); - session.client_channel.send(0x60, 0x00, &session.last_switch_enabled_command, - sizeof(session.last_switch_enabled_command)); + if (ses->last_switch_enabled_command.header.subcommand == 0x05) { + ses->log.info("Switch assist: replaying previous enable command"); + ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command, + sizeof(ses->last_switch_enabled_command)); + ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command, + sizeof(ses->last_switch_enabled_command)); } - session.last_switch_enabled_command = cmd; + ses->last_switch_enabled_command = cmd; } } return HandlerResult::Type::FORWARD; } -static HandlerResult C_V123_A0_A1(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (!session.license) { +static HandlerResult C_V123_A0_A1(shared_ptr ses, uint16_t, uint32_t, string&) { + if (!ses->license) { return HandlerResult::Type::FORWARD; } // For licensed sessions, send them back to newserv's main menu instead of // going to the remote server's ship/block select menu - session.send_to_game_server(); - session.disconnect_action = ProxyServer::LinkedSession::DisconnectAction::CLOSE_IMMEDIATELY; + ses->send_to_game_server(); + ses->disconnect_action = ProxyServer::LinkedSession::DisconnectAction::CLOSE_IMMEDIATELY; return HandlerResult::Type::SUPPRESS; } @@ -2020,40 +1974,39 @@ static on_command_t get_handler(GameVersion version, bool from_server, uint8_t c } void on_proxy_command( - shared_ptr s, - ProxyServer::LinkedSession& session, + shared_ptr ses, bool from_server, uint16_t command, uint32_t flag, string& data) { try { - auto fn = get_handler(session.version, from_server, command); - auto res = fn(s, session, command, flag, data); + auto fn = get_handler(ses->version, from_server, command); + auto res = fn(ses, command, flag, data); if (res.type == HandlerResult::Type::FORWARD) { - forward_command(session, !from_server, command, flag, data, false); + forward_command(ses, !from_server, command, flag, data, false); } else if (res.type == HandlerResult::Type::MODIFIED) { - session.log.info("The preceding command from the %s was modified in transit", + ses->log.info("The preceding command from the %s was modified in transit", from_server ? "server" : "client"); forward_command( - session, + ses, !from_server, res.new_command >= 0 ? res.new_command : command, res.new_flag >= 0 ? res.new_flag : flag, data); } else if (res.type == HandlerResult::Type::SUPPRESS) { - session.log.info("The preceding command from the %s was not forwarded", + ses->log.info("The preceding command from the %s was not forwarded", from_server ? "server" : "client"); } else { throw logic_error("invalid handler result"); } } catch (const exception& e) { - session.log.error("Failed to process command: %s", e.what()); + ses->log.error("Failed to process command: %s", e.what()); if (from_server) { string error_str = "Error: "; error_str += e.what(); - session.send_to_game_server(error_str.c_str()); + ses->send_to_game_server(error_str.c_str()); } else { - session.disconnect(); + ses->disconnect(); } } } diff --git a/src/ProxyCommands.hh b/src/ProxyCommands.hh index e3b1fa5b..37b6f858 100644 --- a/src/ProxyCommands.hh +++ b/src/ProxyCommands.hh @@ -8,8 +8,7 @@ #include "ServerState.hh" void on_proxy_command( - std::shared_ptr s, - ProxyServer::LinkedSession& session, + shared_ptr ses, bool from_server, uint16_t command, uint32_t flag, diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index f9aaa5c2..954749c7 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -145,31 +145,31 @@ void ProxyServer::on_client_connect( this->next_unlicensed_session_id = 0xFF00000000000001; } - auto emplace_ret = this->id_to_session.emplace(session_id, new LinkedSession(this, session_id, listen_port, version, *default_destination)); + auto emplace_ret = this->id_to_session.emplace(session_id, new LinkedSession(this->shared_from_this(), session_id, listen_port, version, *default_destination)); if (!emplace_ret.second) { throw logic_error("linked session already exists for unlicensed client"); } - auto session = emplace_ret.first->second; - session->log.info("Opened linked session"); + auto ses = emplace_ret.first->second; + ses->log.info("Opened linked session"); - Channel ch(bev, version, nullptr, nullptr, session.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN); - session->resume(std::move(ch)); + Channel ch(bev, version, nullptr, nullptr, ses.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN); + ses->resume(std::move(ch)); // If no default destination exists, or the client is not a patch client, // create an unlinked session - we'll have to get the destination from the // client's config, which we'll get via a 9E command soon. } else { - auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(this, bev, listen_port, version)); + auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(this->shared_from_this(), bev, listen_port, version)); if (!emplace_ret.second) { throw logic_error("stale unlinked session exists"); } - auto session = emplace_ret.first->second; + auto ses = emplace_ret.first->second; proxy_server_log.info("Opened unlinked session"); // Note that this should only be set when the linked session is created, not // when it is resumed! if (default_destination) { - session->next_destination = *default_destination; + ses->next_destination = *default_destination; } switch (version) { @@ -183,13 +183,13 @@ void ProxyServer::on_client_connect( uint32_t client_key = random_object(); auto cmd = prepare_server_init_contents_console( server_key, client_key, 0); - session->channel.send(0x02, 0x00, &cmd, sizeof(cmd)); + ses->channel.send(0x02, 0x00, &cmd, sizeof(cmd)); if ((version == GameVersion::DC) || (version == GameVersion::PC)) { - session->channel.crypt_out.reset(new PSOV2Encryption(server_key)); - session->channel.crypt_in.reset(new PSOV2Encryption(client_key)); + ses->channel.crypt_out.reset(new PSOV2Encryption(server_key)); + ses->channel.crypt_in.reset(new PSOV2Encryption(client_key)); } else { - session->channel.crypt_out.reset(new PSOV3Encryption(server_key)); - session->channel.crypt_in.reset(new PSOV3Encryption(client_key)); + ses->channel.crypt_out.reset(new PSOV3Encryption(server_key)); + ses->channel.crypt_in.reset(new PSOV3Encryption(client_key)); } break; } @@ -199,15 +199,15 @@ void ProxyServer::on_client_connect( random_data(server_key.data(), server_key.bytes()); random_data(client_key.data(), client_key.bytes()); auto cmd = prepare_server_init_contents_bb(server_key, client_key, 0); - session->channel.send(0x03, 0x00, &cmd, sizeof(cmd)); - session->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption( + ses->channel.send(0x03, 0x00, &cmd, sizeof(cmd)); + ses->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption( this->state->bb_private_keys, bb_crypt_initial_client_commands, cmd.basic_cmd.client_key.data(), sizeof(cmd.basic_cmd.client_key))); - session->channel.crypt_in = session->detector_crypt; - session->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( - session->detector_crypt, + ses->channel.crypt_in = ses->detector_crypt; + ses->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption( + ses->detector_crypt, cmd.basic_cmd.server_key.data(), sizeof(cmd.basic_cmd.server_key), true)); @@ -220,7 +220,7 @@ void ProxyServer::on_client_connect( } ProxyServer::UnlinkedSession::UnlinkedSession( - ProxyServer* server, + shared_ptr server, struct bufferevent* bev, uint16_t local_port, GameVersion version) @@ -240,8 +240,22 @@ ProxyServer::UnlinkedSession::UnlinkedSession( memset(&this->next_destination, 0, sizeof(this->next_destination)); } +std::shared_ptr ProxyServer::UnlinkedSession::require_server() const { + auto server = this->server.lock(); + if (!server) { + throw logic_error("server is deleted"); + } + return server; +} + +std::shared_ptr ProxyServer::UnlinkedSession::require_server_state() const { + return this->require_server()->state; +} + void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint32_t, std::string& data) { - auto* session = reinterpret_cast(ch.context_obj); + auto* ses = reinterpret_cast(ch.context_obj); + auto server = ses->require_server(); + auto s = server->state; bool should_close_unlinked_session = false; shared_ptr license; @@ -253,12 +267,12 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 string hardware_id; try { - if (session->version == GameVersion::DC) { + if (ses->version == GameVersion::DC) { // We should only get a 93 or 9D while the session is unlinked; if we get // anything else, disconnect if (command == 0x93) { const auto& cmd = check_size_t(data); - license = session->server->state->license_manager->verify_pc( + license = s->license_manager->verify_pc( stoul(cmd.serial_number, nullptr, 16), cmd.access_key); sub_version = cmd.sub_version; language = cmd.language; @@ -267,7 +281,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 client_config.cfg.flags |= Client::Flag::IS_DC_V1; } else if (command == 0x9D) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DC_GC_9D)); - license = session->server->state->license_manager->verify_pc( + license = s->license_manager->verify_pc( stoul(cmd.serial_number, nullptr, 16), cmd.access_key); sub_version = cmd.sub_version; language = cmd.language; @@ -276,20 +290,20 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 throw runtime_error("command is not 93 or 9D"); } - } else if (session->version == GameVersion::PC) { + } else if (ses->version == GameVersion::PC) { // We should only get a 9D while the session is unlinked; if we get // anything else, disconnect if (command != 0x9D) { throw runtime_error("command is not 9D"); } const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_PC_9D)); - license = session->server->state->license_manager->verify_pc( + license = s->license_manager->verify_pc( stoul(cmd.serial_number, nullptr, 16), cmd.access_key); sub_version = cmd.sub_version; language = cmd.language; character_name = cmd.name; - } else if (session->version == GameVersion::GC) { + } else if (ses->version == GameVersion::GC) { // We should only get a 9E while the session is unlinked; if we get // anything else, disconnect // TODO: GCTE will send 9D; we should presumably handle that too, sigh @@ -297,17 +311,17 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 throw runtime_error("command is not 9E"); } const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_GC_9E)); - license = session->server->state->license_manager->verify_gc( + license = s->license_manager->verify_gc( stoul(cmd.serial_number, nullptr, 16), cmd.access_key); sub_version = cmd.sub_version; language = cmd.language; character_name = cmd.name; client_config.cfg = cmd.client_config.cfg; - } else if (session->version == GameVersion::XB) { + } else if (ses->version == GameVersion::XB) { throw runtime_error("xbox licenses are not implemented"); - } else if (session->version == GameVersion::BB) { + } else if (ses->version == GameVersion::BB) { // We should only get a 93 while the session is unlinked; if we get // anything else, disconnect if (command != 0x93) { @@ -315,15 +329,15 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 } const auto& cmd = check_size_t(data); try { - license = session->server->state->license_manager->verify_bb( + license = s->license_manager->verify_bb( cmd.username, cmd.password); } catch (const missing_license&) { - if (!session->server->state->allow_unregistered_users) { + if (!s->allow_unregistered_users) { throw; } shared_ptr l = LicenseManager::create_license_bb( fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true); - session->server->state->license_manager->add(l); + s->license_manager->add(l); license = l; } login_command_bb = std::move(data); @@ -333,7 +347,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 } } catch (const exception& e) { - session->log.error("Failed to process command from unlinked client: %s", e.what()); + ses->log.error("Failed to process command from unlinked client: %s", e.what()); should_close_unlinked_session = true; } @@ -350,94 +364,84 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 should_close_unlinked_session = true; // Look up the linked session for this license (if any) - shared_ptr linked_session; + shared_ptr linked_ses; try { - linked_session = session->server->id_to_session.at(license->serial_number); - linked_session->log.info("Resuming linked session from unlinked session"); + linked_ses = server->id_to_session.at(license->serial_number); + linked_ses->log.info("Resuming linked session from unlinked session"); } catch (const out_of_range&) { // 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_session.reset(new LinkedSession( - session->server, - session->local_port, - session->version, - license, - client_config)); - linked_session->log.info("Opened licensed session for unlinked session based on client config"); - } else if (session->next_destination.ss_family == AF_INET) { - linked_session.reset(new LinkedSession( - session->server, - session->local_port, - session->version, - license, - session->next_destination)); - linked_session->log.info("Opened licensed session for unlinked session based on unlinked default destination"); + linked_ses.reset(new LinkedSession( + server, ses->local_port, ses->version, license, client_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->log.info("Opened licensed session for unlinked session based on unlinked default destination"); } else { - session->log.error("Cannot open linked session: no valid destination in client config or unlinked session"); + ses->log.error("Cannot open linked session: no valid destination in client config or unlinked session"); } } - if (linked_session.get()) { - session->server->id_to_session.emplace(license->serial_number, linked_session); - if (linked_session->version != session->version) { - linked_session->log.error("Linked session has different game version"); + if (linked_ses.get()) { + server->id_to_session.emplace(license->serial_number, linked_ses); + if (linked_ses->version != ses->version) { + linked_ses->log.error("Linked session has different game version"); } else { // Resume the linked session using the unlinked session try { - if (session->version == GameVersion::BB) { - linked_session->resume( - std::move(session->channel), - session->detector_crypt, + if (ses->version == GameVersion::BB) { + linked_ses->resume( + std::move(ses->channel), + ses->detector_crypt, std::move(login_command_bb)); } else { - linked_session->resume( - std::move(session->channel), - session->detector_crypt, + linked_ses->resume( + std::move(ses->channel), + ses->detector_crypt, sub_version, language, character_name, hardware_id); } } catch (const exception& e) { - linked_session->log.error("Failed to resume linked session: %s", e.what()); + linked_ses->log.error("Failed to resume linked session: %s", e.what()); } } } } if (should_close_unlinked_session) { - session->server->delete_session(session_key); + server->delete_session(session_key); } } void ProxyServer::UnlinkedSession::on_error(Channel& ch, short events) { - auto* session = reinterpret_cast(ch.context_obj); + auto* ses = reinterpret_cast(ch.context_obj); if (events & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); - session->log.warning("Error %d (%s) in unlinked client stream", err, + ses->log.warning("Error %d (%s) in unlinked client stream", err, evutil_socket_error_to_string(err)); } if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) { - session->log.info("Client has disconnected"); - session->server->delete_session(session->channel.bev.get()); + ses->log.info("Client has disconnected"); + ses->require_server()->delete_session(ses->channel.bev.get()); } } ProxyServer::LinkedSession::LinkedSession( - ProxyServer* server, + shared_ptr server, uint64_t id, uint16_t local_port, GameVersion version) : server(server), id(id), log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id), proxy_server_log.min_level), - timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT, - &LinkedSession::dispatch_on_timeout, this), - event_free), + timeout_event(event_new(server->base.get(), -1, EV_TIMEOUT, &LinkedSession::dispatch_on_timeout, this), event_free), license(nullptr), client_channel( version, @@ -477,7 +481,7 @@ ProxyServer::LinkedSession::LinkedSession( } ProxyServer::LinkedSession::LinkedSession( - ProxyServer* server, + shared_ptr server, uint16_t local_port, GameVersion version, shared_ptr license, @@ -493,7 +497,7 @@ ProxyServer::LinkedSession::LinkedSession( } ProxyServer::LinkedSession::LinkedSession( - ProxyServer* server, + shared_ptr server, uint16_t local_port, GameVersion version, std::shared_ptr license, @@ -504,7 +508,7 @@ ProxyServer::LinkedSession::LinkedSession( } ProxyServer::LinkedSession::LinkedSession( - ProxyServer* server, + shared_ptr server, uint64_t id, uint16_t local_port, GameVersion version, @@ -513,6 +517,18 @@ ProxyServer::LinkedSession::LinkedSession( this->next_destination = destination; } +shared_ptr ProxyServer::LinkedSession::require_server() const { + auto server = this->server.lock(); + if (!server) { + throw logic_error("server is deleted"); + } + return server; +} + +std::shared_ptr ProxyServer::LinkedSession::require_server_state() const { + return this->require_server()->state; +} + void ProxyServer::LinkedSession::resume( Channel&& client_channel, shared_ptr detector_crypt, @@ -579,7 +595,7 @@ void ProxyServer::LinkedSession::connect() { this->log.info("Connecting to %s", netloc_str.c_str()); this->server_channel.set_bufferevent(bufferevent_socket_new( - this->server->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS)); + this->require_server()->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS)); if (bufferevent_socket_connect(this->server_channel.bev.get(), reinterpret_cast(dest_sin), sizeof(*dest_sin)) != 0) { throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR())); @@ -617,38 +633,38 @@ void ProxyServer::LinkedSession::dispatch_on_timeout( } void ProxyServer::LinkedSession::on_timeout() { - this->server->delete_session(this->id); + this->require_server()->delete_session(this->id); } void ProxyServer::LinkedSession::on_error(Channel& ch, short events) { - auto* session = reinterpret_cast(ch.context_obj); - bool is_server_stream = (&ch == &session->server_channel); + auto* ses = reinterpret_cast(ch.context_obj); + bool is_server_stream = (&ch == &ses->server_channel); if (events & BEV_EVENT_CONNECTED) { - session->log.info("%s channel connected", is_server_stream ? "Server" : "Client"); + ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client"); - if (is_server_stream && (session->options.override_lobby_event >= 0) && - (((session->version == GameVersion::GC) && !(session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) || - (session->version == GameVersion::XB) || - (session->version == GameVersion::BB))) { - session->client_channel.send(0xDA, session->options.override_lobby_event); + 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)) || + (ses->version == GameVersion::XB) || + (ses->version == GameVersion::BB))) { + ses->client_channel.send(0xDA, ses->options.override_lobby_event); } } if (events & BEV_EVENT_ERROR) { int err = EVUTIL_SOCKET_ERROR(); - session->log.warning("Error %d (%s) in %s stream", + ses->log.warning("Error %d (%s) in %s stream", err, evutil_socket_error_to_string(err), is_server_stream ? "server" : "client"); } if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { - session->log.info("%s has disconnected", + ses->log.info("%s has disconnected", is_server_stream ? "Server" : "Client"); // If the server disconnected, send the client back to the game server so // they're not disconnected completely. if (is_server_stream) { - session->send_to_game_server("The server has\ndisconnected."); + ses->send_to_game_server("The server has\ndisconnected."); } - session->disconnect(); + ses->disconnect(); } } @@ -683,7 +699,8 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) this->client_channel.send(this->is_in_game ? 0x66 : 0x69, leaving_id, &cmd, sizeof(cmd)); } - string encoded_name = encode_sjis(this->server->state->name); + auto s = this->require_server_state(); + string encoded_name = encode_sjis(s->name); if (this->is_in_game) { send_ship_info(this->client_channel, decode_sjis(string_printf("You cannot return\nto $C6%s$C7\nwhile in a game.\n\n%s", encoded_name.c_str(), error_message ? error_message : ""))); this->disconnect(); @@ -701,7 +718,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) const auto& port_name = version_to_login_port_name.at(static_cast( this->version)); - S_Reconnect_19 reconnect_cmd = {{0, this->server->state->name_to_port_config.at(port_name)->port, 0}}; + S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}}; // If the client is on a virtual connection, we can use any address // here and they should be able to connect back to the game server. If @@ -760,25 +777,24 @@ bool ProxyServer::LinkedSession::is_connected() const { } void ProxyServer::LinkedSession::on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) { - auto* session = reinterpret_cast(ch.context_obj); - bool is_server_stream = (&ch == &session->server_channel); + auto* ses = reinterpret_cast(ch.context_obj); + bool is_server_stream = (&ch == &ses->server_channel); try { if (is_server_stream) { - size_t bytes_to_save = min(data.size(), sizeof(session->prev_server_command_bytes)); - memcpy(session->prev_server_command_bytes, data.data(), bytes_to_save); + size_t bytes_to_save = min(data.size(), sizeof(ses->prev_server_command_bytes)); + memcpy(ses->prev_server_command_bytes, data.data(), bytes_to_save); } on_proxy_command( - session->server->state, - *session, + ses->shared_from_this(), is_server_stream, command, flag, data); } catch (const exception& e) { - session->log.error("Failed to process command from %s: %s", + ses->log.error("Failed to process command from %s: %s", is_server_stream ? "server" : "client", e.what()); - session->disconnect(); + ses->disconnect(); } } @@ -808,7 +824,7 @@ shared_ptr ProxyServer::create_licensed_session( shared_ptr l, uint16_t local_port, GameVersion version, const ClientConfigBB& newserv_client_config) { shared_ptr session(new LinkedSession( - this, local_port, version, l, newserv_client_config)); + this->shared_from_this(), local_port, version, l, newserv_client_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 1fe1e0ff..8a8ef8ba 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -16,7 +16,7 @@ #include "PSOProtocol.hh" #include "ServerState.hh" -class ProxyServer { +class ProxyServer : public std::enable_shared_from_this { public: ProxyServer() = delete; ProxyServer(const ProxyServer&) = delete; @@ -31,8 +31,8 @@ public: void connect_client(struct bufferevent* bev, uint16_t server_port); - struct LinkedSession { - ProxyServer* server; + struct LinkedSession : std::enable_shared_from_this { + std::weak_ptr server; uint64_t id; PrefixedLogger log; @@ -77,13 +77,10 @@ public: uint32_t next_item_id; struct LobbyPlayer { - uint32_t guild_card_number; + uint32_t guild_card_number = 0; std::string name; - uint8_t section_id; - uint8_t char_class; - LobbyPlayer() : guild_card_number(0), - section_id(0), - char_class(0) {} + uint8_t section_id = 0; + uint8_t char_class = 0; }; std::vector lobby_players; size_t lobby_client_id; @@ -115,29 +112,32 @@ public: // TODO: This first constructor should be private LinkedSession( - ProxyServer* server, + std::shared_ptr server, uint64_t id, uint16_t local_port, GameVersion version); LinkedSession( - ProxyServer* server, + std::shared_ptr server, uint16_t local_port, GameVersion version, std::shared_ptr license, const ClientConfigBB& newserv_client_config); LinkedSession( - ProxyServer* server, + std::shared_ptr server, uint16_t local_port, GameVersion version, std::shared_ptr license, const struct sockaddr_storage& next_destination); LinkedSession( - ProxyServer* server, + std::shared_ptr server, uint64_t id, uint16_t local_port, GameVersion version, const struct sockaddr_storage& next_destination); + std::shared_ptr require_server() const; + std::shared_ptr require_server_state() const; + void resume( Channel&& client_channel, std::shared_ptr detector_crypt, @@ -205,7 +205,7 @@ private: }; struct UnlinkedSession { - ProxyServer* server; + std::weak_ptr server; PrefixedLogger log; Channel channel; @@ -215,7 +215,10 @@ private: std::shared_ptr detector_crypt; - UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version); + UnlinkedSession(std::shared_ptr server, struct bufferevent* bev, uint16_t port, GameVersion version); + + std::shared_ptr require_server() const; + std::shared_ptr require_server_state() const; void receive_and_process_commands(); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 4cac2007..24b2b9e4 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -30,8 +30,9 @@ const char* BATTLE_TABLE_DISCONNECT_HOOK_NAME = "battle_table_state"; const char* QUEST_BARRIER_DISCONNECT_HOOK_NAME = "quest_barrier"; const char* ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME = "add_next_game_client"; -static shared_ptr proxy_options_menu_for_client( - shared_ptr s, shared_ptr c) { +static shared_ptr proxy_options_menu_for_client(shared_ptr c) { + auto s = c->require_server_state(); + shared_ptr ret(new Menu(MenuID::PROXY_OPTIONS, u"Proxy options")); ret->items.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", u"Return to the\nProxy Server menu", 0); @@ -85,13 +86,16 @@ static shared_ptr proxy_options_menu_for_client( return ret; } -static void send_client_to_lobby_server(shared_ptr s, shared_ptr c) { +static void send_client_to_lobby_server(shared_ptr c) { + auto s = c->require_server_state(); const auto& port_name = version_to_lobby_port_name.at(static_cast(c->version())); send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port); } -static void send_client_to_proxy_server(shared_ptr s, shared_ptr c) { +static void send_client_to_proxy_server(shared_ptr c) { + auto s = c->require_server_state(); + const auto& port_name = version_to_proxy_port_name.at(static_cast(c->version())); uint16_t local_port = s->name_to_port_config.at(port_name)->port; @@ -108,16 +112,17 @@ static void send_client_to_proxy_server(shared_ptr s, shared_ptrconnect_address_for_client(c), local_port); } -static void send_proxy_destinations_menu(shared_ptr s, shared_ptr c) { +static void send_proxy_destinations_menu(shared_ptr c) { + auto s = c->require_server_state(); send_menu(c, s->proxy_destinations_menu_for_version(c->version())); } -static bool send_enable_send_function_call_if_applicable( - shared_ptr s, 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 (s->episode_3_send_function_call_enabled) { - send_quest_buffer_overflow(s, c); + send_quest_buffer_overflow(c); } else { c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL; } @@ -129,9 +134,10 @@ static bool send_enable_send_function_call_if_applicable( //////////////////////////////////////////////////////////////////////////////// -void on_connect(std::shared_ptr s, std::shared_ptr c) { +void on_connect(std::shared_ptr c) { switch (c->server_behavior) { case ServerBehavior::PC_CONSOLE_DETECT: { + auto s = c->require_server_state(); uint16_t pc_port = s->name_to_port_config.at("pc-login")->port; uint16_t console_port = s->name_to_port_config.at("console-login")->port; send_pc_console_split_reconnect(c, s->connect_address_for_client(c), pc_port, console_port); @@ -140,18 +146,18 @@ void on_connect(std::shared_ptr s, std::shared_ptr c) { } case ServerBehavior::LOGIN_SERVER: - send_server_init(s, c, SendServerInitFlag::IS_INITIAL_CONNECTION); + send_server_init(c, SendServerInitFlag::IS_INITIAL_CONNECTION); break; case ServerBehavior::PATCH_SERVER_BB: c->flags |= Client::Flag::IS_BB_PATCH; - send_server_init(s, c, 0); + send_server_init(c, 0); break; case ServerBehavior::PATCH_SERVER_PC: case ServerBehavior::DATA_SERVER_BB: case ServerBehavior::LOBBY_SERVER: - send_server_init(s, c, 0); + send_server_init(c, 0); break; default: @@ -160,9 +166,10 @@ void on_connect(std::shared_ptr s, std::shared_ptr c) { } } -static void send_main_menu(shared_ptr s, shared_ptr c) { - shared_ptr main_menu(new Menu(MenuID::MAIN, s->name)); +static void send_main_menu(shared_ptr c) { + auto s = c->require_server_state(); + shared_ptr main_menu(new Menu(MenuID::MAIN, s->name)); main_menu->items.emplace_back( MainMenuItemID::GO_TO_LOBBY, u"Go to lobby", [s, wc = weak_ptr(c)]() -> u16string { @@ -229,11 +236,13 @@ static void send_main_menu(shared_ptr s, shared_ptr c) { send_menu(c, main_menu); } -void on_login_complete(shared_ptr s, shared_ptr c) { +void on_login_complete(shared_ptr c) { // On the BB data server, this function is called only on the last connection // (when we should send the ship select menu). if ((c->server_behavior == ServerBehavior::LOGIN_SERVER) || (c->server_behavior == ServerBehavior::DATA_SERVER_BB)) { + auto s = c->require_server_state(); + // 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) { @@ -243,7 +252,7 @@ void on_login_complete(shared_ptr s, shared_ptr c) { send_change_event(c, s->pre_lobby_event); } - send_ep3_rank_update(s, c); + send_ep3_rank_update(c); send_get_player_info(c); } else if (s->pre_lobby_event) { @@ -254,10 +263,10 @@ void on_login_complete(shared_ptr s, shared_ptr c) { (c->flags & Client::Flag::NO_D6) || !(c->flags & Client::Flag::AT_WELCOME_MESSAGE)) { c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE; - if (send_enable_send_function_call_if_applicable(s, c)) { + if (send_enable_send_function_call_if_applicable(c)) { send_update_client_config(c); } - send_main_menu(s, c); + send_main_menu(c); } else { send_message_box(c, s->welcome_message.c_str()); } @@ -270,15 +279,19 @@ void on_login_complete(shared_ptr s, shared_ptr c) { c->game_data.should_update_play_time = true; } - send_lobby_list(c, s); + send_lobby_list(c); send_get_player_info(c); } } -void on_disconnect(shared_ptr s, shared_ptr c) { +void on_disconnect(shared_ptr c) { // If the client was in a lobby, remove them and notify the other clients - if (c->lobby_id) { - s->remove_client_from_lobby(c); + auto l = c->lobby.lock(); + if (l) { + auto server = c->server.lock(); + if (server) { + server->get_state()->remove_client_from_lobby(c); + } } // Note: The client's GameData destructor should save their player data @@ -287,8 +300,7 @@ void on_disconnect(shared_ptr s, shared_ptr c) { //////////////////////////////////////////////////////////////////////////////// -static void set_console_client_flags( - shared_ptr c, uint32_t sub_version) { +static void set_console_client_flags(shared_ptr c, uint32_t sub_version) { if (c->channel.crypt_in->type() == PSOEncryption::Type::V2) { if (sub_version < 0x28) { c->channel.version = GameVersion::DC; @@ -304,9 +316,9 @@ static void set_console_client_flags( } } -static void on_DB_V3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); if (c->channel.crypt_in->type() == PSOEncryption::Type::V2) { throw runtime_error("GC trial edition client sent V3 verify license command"); @@ -345,9 +357,10 @@ static void on_DB_V3(shared_ptr s, shared_ptr c, } } -static void on_88_DCNTE(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(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; @@ -377,9 +390,10 @@ static void on_88_DCNTE(shared_ptr s, shared_ptr c, } } -static void on_8B_DCNTE(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); + 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; @@ -417,13 +431,14 @@ static void on_8B_DCNTE(shared_ptr s, shared_ptr c, if (!c->should_disconnect) { send_update_client_config(c); - on_login_complete(s, c); + on_login_complete(c); } } -static void on_90_DC(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_90_DC(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data, 0xFFFF); + 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; @@ -453,8 +468,7 @@ static void on_90_DC(shared_ptr s, shared_ptr c, } } -static void on_92_DC(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_92_DC(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_t(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 @@ -463,9 +477,10 @@ static void on_92_DC(shared_ptr, shared_ptr c, send_command(c, 0x92, 0x01); } -static void on_93_DC(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_93_DC(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtendedV1_DC_93)); + auto s = c->require_server_state(); + set_console_client_flags(c, cmd.sub_version); uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); @@ -511,13 +526,14 @@ static void on_93_DC(shared_ptr s, shared_ptr c, send_command(c, 0x90, 0x01); c->flags |= (Client::Flag::CHECKED_FOR_DC_V1_PROTOTYPE | Client::Flag::IS_DC_V1_PROTOTYPE); } else { - on_login_complete(s, c); + on_login_complete(c); } } -static void on_9A(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_9A(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); + set_console_client_flags(c, cmd.sub_version); uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); @@ -571,9 +587,10 @@ static void on_9A(shared_ptr s, shared_ptr c, } } -static void on_9C(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_9C(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); + set_console_client_flags(c, cmd.sub_version); uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); @@ -632,9 +649,10 @@ static void on_9C(shared_ptr s, shared_ptr c, } } -static void on_9D_9E(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t, const string& data) { +static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, const string& data) { const C_Login_DC_PC_GC_9D* base_cmd; + auto s = c->require_server_state(); + if (command == 0x9D) { base_cmd = &check_size_t(data, sizeof(C_LoginExtended_PC_9D)); if (base_cmd->is_extended) { @@ -751,13 +769,12 @@ static void on_9D_9E(shared_ptr s, shared_ptr c, } send_update_client_config(c); - on_login_complete(s, c); + on_login_complete(c); } -static void on_93_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { - const auto& cmd = check_size_t(data, - sizeof(C_Login_BB_93) - 8, sizeof(C_Login_BB_93)); +static void on_93_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { + const auto& cmd = check_size_t(data, sizeof(C_Login_BB_93) - 8, sizeof(C_Login_BB_93)); + auto s = c->require_server_state(); bool is_old_format; if (data.size() == sizeof(C_Login_BB_93) - 8) { @@ -829,7 +846,7 @@ static void on_93_BB(shared_ptr s, shared_ptr c, break; case ClientStateBB::SHIP_SELECT: - on_login_complete(s, c); + on_login_complete(c); break; case ClientStateBB::IN_GAME: @@ -840,8 +857,7 @@ static void on_93_BB(shared_ptr s, shared_ptr c, } } -static void on_9F_V3(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_9F_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { if (c->version() == GameVersion::BB) { const auto& cfg = check_size_t(data); c->import_config(cfg); @@ -851,14 +867,12 @@ static void on_9F_V3(shared_ptr, shared_ptr c, } } -static void on_96(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_96(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_t(data); send_server_time(c); } -static void on_B1(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_B1(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); send_server_time(c); // The B1 command is sent in response to a 97 command, which is normally part @@ -871,22 +885,22 @@ static void on_B1(shared_ptr s, shared_ptr c, // joining the lobby. This is why we delay the 19 command until the client // responds after saving. if (c->should_send_to_lobby_server) { - send_client_to_lobby_server(s, c); + send_client_to_lobby_server(c); } else if (c->should_send_to_proxy_server) { - send_client_to_proxy_server(s, c); + send_client_to_proxy_server(c); } } -static void on_BA_Ep3(shared_ptr s, - shared_ptr c, uint16_t command, uint32_t, const string& data) { +static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, const string& data) { const auto& in_cmd = check_size_t(data); + auto s = c->require_server_state(); uint32_t meseta = s->ep3_infinite_meseta ? 1000000 : 0; S_Meseta_GC_Ep3_BA out_cmd = {meseta, meseta, in_cmd.request_token}; send_command(c, command, 0x03, &out_cmd, sizeof(out_cmd)); } -static bool add_next_game_client(shared_ptr s, shared_ptr l) { +static bool add_next_game_client(shared_ptr l) { auto it = l->clients_to_add.begin(); if (it == l->clients_to_add.end()) { return false; @@ -908,6 +922,7 @@ static bool add_next_game_client(shared_ptr s, shared_ptr l) throw logic_error("client id is already in use"); } + auto s = c->require_server_state(); if (tourn) { G_SetStateFlags_GC_Ep3_6xB4x03 state_cmd; state_cmd.state.turn_num = 1; @@ -932,17 +947,14 @@ static bool add_next_game_client(shared_ptr s, shared_ptr l) s->change_client_lobby(c, l, true, target_client_id); c->flags |= Client::Flag::LOADING; c->disconnect_hooks.emplace(ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME, [s, l]() -> void { - add_next_game_client(s, l); + add_next_game_client(l); }); return true; } static bool start_ep3_battle_table_game_if_ready( - shared_ptr s, - shared_ptr l, - int16_t table_number, - int16_t tournament_table_number) { + shared_ptr l, int16_t table_number, int16_t tournament_table_number) { if (table_number < 0) { // Negative numbers are supposed to mean the client is not seated at a // table, so it's an error for this function to be called with a negative @@ -1041,6 +1053,7 @@ static bool start_ep3_battle_table_game_if_ready( } else { // If there is already a game for this match, don't allow a new one to // start + auto s = l->require_server_state(); for (auto l : s->all_lobbies()) { if (l->tournament_match == tourn_match) { tourn_match.reset(); @@ -1070,10 +1083,12 @@ static bool start_ep3_battle_table_game_if_ready( // begin, but create_game_generic can still return null if an internal // precondition fails (though this should never happen for Episode 3 games). + auto c = game_clients.begin()->second; + auto s = c->require_server_state(); uint32_t flags = Lobby::Flag::NON_V1_ONLY; u16string name = tourn ? decode_sjis(tourn->get_name()) : u""; auto game = create_game_generic( - s, game_clients.begin()->second, name, u"", Episode::EP3, + s, c, name, u"", Episode::EP3, GameMode::NORMAL, 0, flags); if (!game) { return false; @@ -1118,21 +1133,19 @@ static bool start_ep3_battle_table_game_if_ready( // Add the first client to the game (the remaining clients will be added when // the previous is done loading) - add_next_game_client(s, game); + add_next_game_client(game); return true; } -static void on_ep3_battle_table_state_updated( - shared_ptr s, shared_ptr l, int16_t table_number) { +static void on_ep3_battle_table_state_updated(shared_ptr l, int16_t table_number) { send_ep3_card_battle_table_state(l, table_number); - start_ep3_battle_table_game_if_ready(s, l, table_number, 2); + start_ep3_battle_table_game_if_ready(l, table_number, 2); } -static void on_E4_Ep3(shared_ptr s, - shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_E4_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { const auto& cmd = check_size_t(data); - auto l = s->find_lobby(c->lobby_id); + auto l = c->require_lobby(); if (cmd.seat_number >= 4) { throw runtime_error("invalid seat number"); @@ -1152,18 +1165,18 @@ static void on_E4_Ep3(shared_ptr s, c->card_battle_table_seat_state = 0; } - on_ep3_battle_table_state_updated(s, l, cmd.table_number); + on_ep3_battle_table_state_updated(l, cmd.table_number); bool should_have_disconnect_hook = (c->card_battle_table_number != -1); if (should_have_disconnect_hook && !c->disconnect_hooks.count(BATTLE_TABLE_DISCONNECT_HOOK_NAME)) { - c->disconnect_hooks.emplace(BATTLE_TABLE_DISCONNECT_HOOK_NAME, [s, l, c]() -> void { + c->disconnect_hooks.emplace(BATTLE_TABLE_DISCONNECT_HOOK_NAME, [l, c]() -> void { int16_t table_number = c->card_battle_table_number; c->card_battle_table_number = -1; c->card_battle_table_seat_number = 0; c->card_battle_table_seat_state = 0; if (table_number != -1) { - on_ep3_battle_table_state_updated(s, l, c->card_battle_table_number); + on_ep3_battle_table_state_updated(l, c->card_battle_table_number); } }); } else if (!should_have_disconnect_hook) { @@ -1171,10 +1184,9 @@ static void on_E4_Ep3(shared_ptr s, } } -static void on_E5_Ep3(shared_ptr s, - shared_ptr c, uint16_t, uint32_t flag, const string& data) { +static void on_E5_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { check_size_t(data); - auto l = s->find_lobby(c->lobby_id); + auto l = c->require_lobby(); if (l->is_game() || !l->is_ep3()) { throw runtime_error("battle table command sent in non-CARD lobby"); } @@ -1188,17 +1200,14 @@ static void on_E5_Ep3(shared_ptr s, } else { c->card_battle_table_seat_state = 3; } - on_ep3_battle_table_state_updated(s, l, c->card_battle_table_number); + on_ep3_battle_table_state_updated(l, c->card_battle_table_number); } -static void on_DC_Ep3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t flag, const string& data) { +static void on_DC_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { check_size_v(data.size(), 0); - shared_ptr l; - try { - l = s->find_lobby(c->lobby_id); - } catch (const out_of_range&) { + auto l = c->lobby.lock(); + if (!l) { return; } @@ -1207,7 +1216,7 @@ static void on_DC_Ep3(shared_ptr s, shared_ptr c, if (l->tournament_match) { auto tourn = l->tournament_match->tournament.lock(); if (tourn) { - send_ep3_set_tournament_player_decks(s, l, c, l->tournament_match); + send_ep3_set_tournament_player_decks(c); string data = Episode3::Server::prepare_6xB6x41_map_definition( tourn->get_map(), l->flags & Lobby::Flag::IS_EP3_TRIAL); c->channel.send(0x6C, 0x00, data); @@ -1221,7 +1230,7 @@ static void on_DC_Ep3(shared_ptr s, shared_ptr c, static void on_tournament_bracket_updated( shared_ptr s, shared_ptr tourn) { - tourn->send_all_state_updates(s); + tourn->send_all_state_updates(); if (tourn->get_state() == Episode3::Tournament::State::COMPLETE) { auto team = tourn->get_winner_team(); if (!team->has_any_human_players()) { @@ -1235,12 +1244,9 @@ static void on_tournament_bracket_updated( } } -static void on_CA_Ep3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { - shared_ptr l; - try { - l = s->find_lobby(c->lobby_id); - } catch (const out_of_range&) { +static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, const string& data) { + auto l = c->lobby.lock(); + if (!l) { // In rare cases (e.g. when two players end a tournament's match results // screens at exactly the same time), the client can send a server data // command when it's not in any lobby at all. We just ignore such commands. @@ -1249,6 +1255,7 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, if (!l->is_game() || !l->is_ep3()) { throw runtime_error("Episode 3 server data request sent outside of Episode 3 game"); } + auto s = l->require_server_state(); const auto& header = check_size_t(data, 0xFFFF); if (header.subcommand != 0xB3) { @@ -1256,6 +1263,8 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, } if (!l->ep3_server || l->ep3_server->battle_finished) { + auto s = c->require_server_state(); + if (!l->ep3_server) { l->log.info("Creating Episode 3 server state"); } else { @@ -1322,18 +1331,17 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, } else { throw logic_error("invalid winner team id"); } - send_ep3_tournament_match_result(s, l, l->tournament_match); + send_ep3_tournament_match_result(l); on_tournament_bracket_updated(s, tourn); l->ep3_server->tournament_match_result_sent = true; } } -static void on_E2_Ep3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t flag, const string&) { +static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string&) { switch (flag) { case 0x00: // Request tournament list - send_ep3_tournament_list(s, c, false); + send_ep3_tournament_list(c, false); break; case 0x01: { // Check tournament auto team = c->ep3_tournament_team.lock(); @@ -1355,13 +1363,14 @@ static void on_E2_Ep3(shared_ptr s, shared_ptr c, auto tourn = team->tournament.lock(); if (tourn) { if (tourn->get_state() != Episode3::Tournament::State::COMPLETE) { + auto s = c->require_server_state(); team->unregister_player(c->license->serial_number); on_tournament_bracket_updated(s, tourn); } c->ep3_tournament_team.reset(); } } - send_ep3_confirm_tournament_entry(s, c, nullptr); + send_ep3_confirm_tournament_entry(c, nullptr); break; } case 0x03: // Create tournament spectator team (get battle list) @@ -1373,22 +1382,22 @@ static void on_E2_Ep3(shared_ptr s, shared_ptr c, } } -static void on_D6_V3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_D6_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); if (c->flags & 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) { - send_enable_send_function_call_if_applicable(s, c); + send_enable_send_function_call_if_applicable(c); c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE; send_update_client_config(c); - send_main_menu(s, c); + send_main_menu(c); } } -static void on_09(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); switch (cmd.menu_id) { case MenuID::QUEST_FILTER: @@ -1397,24 +1406,23 @@ static void on_09(shared_ptr s, shared_ptr c, // usual location on the screen. break; case MenuID::QUEST: { + bool is_download_quest = !c->lobby.lock(); if (!s->quest_index) { - send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id); + send_quest_info(c, u"$C6Quests are not available.", is_download_quest); } else { auto q = s->quest_index->get(c->quest_version(), cmd.item_id); if (!q) { - send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id); + send_quest_info(c, u"$C4Quest does not\nexist.", is_download_quest); } else { - send_quest_info(c, q->long_description.c_str(), !c->lobby_id); + send_quest_info(c, q->long_description.c_str(), is_download_quest); } } break; } case MenuID::GAME: { - shared_ptr game; - try { - game = s->find_lobby(cmd.item_id); - } catch (const out_of_range& e) { + auto game = s->find_lobby(cmd.item_id); + if (!game) { send_ship_info(c, u"$C4Game no longer\nexists."); break; } @@ -1555,8 +1563,7 @@ static void on_09(shared_ptr s, shared_ptr c, } } -static void on_10(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) { bool uses_unicode = ((c->version() == GameVersion::PC) || (c->version() == GameVersion::BB)); uint32_t menu_id; @@ -1601,27 +1608,30 @@ static void on_10(shared_ptr s, shared_ptr c, // 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))) { - send_client_to_lobby_server(s, c); + send_client_to_lobby_server(c); } else { send_command(c, 0x97, 0x01); send_update_client_config(c); } } else { - send_client_to_lobby_server(s, c); + send_client_to_lobby_server(c); } break; } - case MainMenuItemID::INFORMATION: + 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; break; + } case MainMenuItemID::PROXY_DESTINATIONS: - send_proxy_destinations_menu(s, c); + send_proxy_destinations_menu(c); break; - case MainMenuItemID::DOWNLOAD_QUESTS: + case MainMenuItemID::DOWNLOAD_QUESTS: { + auto s = c->require_server_state(); if (c->flags & 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 @@ -1643,6 +1653,7 @@ static void on_10(shared_ptr s, shared_ptr c, send_quest_menu(c, MenuID::QUEST_FILTER, s->quest_category_index, flags); } break; + } case MainMenuItemID::PATCHES: if (!function_compiler_available()) { @@ -1651,8 +1662,8 @@ static void on_10(shared_ptr s, shared_ptr c, if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { throw runtime_error("client does not support send_function_call"); } - prepare_client_for_patches(s, c, [s, c]() -> void { - send_menu(c, s->function_code_index->patch_menu(c->specific_version)); + prepare_client_for_patches(c, [c]() -> void { + send_menu(c, c->require_server_state()->function_code_index->patch_menu(c->specific_version)); }); break; @@ -1663,8 +1674,8 @@ static void on_10(shared_ptr s, shared_ptr c, if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { throw runtime_error("client does not support send_function_call"); } - prepare_client_for_patches(s, c, [s, c]() -> void { - send_menu(c, s->dol_file_index->menu); + prepare_client_for_patches(c, [c]() -> void { + send_menu(c, c->require_server_state()->dol_file_index->menu); }); break; @@ -1687,11 +1698,11 @@ static void on_10(shared_ptr s, shared_ptr c, case MenuID::INFORMATION: { if (item_id == InformationMenuItemID::GO_BACK) { c->flags &= ~Client::Flag::IN_INFORMATION_MENU; - send_main_menu(s, c); + send_main_menu(c); } else { try { - send_message_box(c, s->information_contents->at(item_id).c_str()); + send_message_box(c, c->require_server_state()->information_contents->at(item_id).c_str()); } catch (const out_of_range&) { send_message_box(c, u"$C6No such information exists."); } @@ -1702,7 +1713,7 @@ static void on_10(shared_ptr s, shared_ptr c, case MenuID::PROXY_OPTIONS: { switch (item_id) { case ProxyOptionsMenuItemID::GO_BACK: - send_proxy_destinations_menu(s, c); + send_proxy_destinations_menu(c); break; case ProxyOptionsMenuItemID::CHAT_COMMANDS: c->options.enable_chat_commands = !c->options.enable_chat_commands; @@ -1757,7 +1768,7 @@ static void on_10(shared_ptr s, shared_ptr c, case ProxyOptionsMenuItemID::SKIP_CARD: c->options.zero_remote_guild_card = !c->options.zero_remote_guild_card; resend_proxy_options_menu: - send_menu(c, proxy_options_menu_for_client(s, c)); + send_menu(c, proxy_options_menu_for_client(c)); break; default: send_message_box(c, u"Incorrect menu item ID."); @@ -1768,12 +1779,13 @@ static void on_10(shared_ptr s, shared_ptr c, case MenuID::PROXY_DESTINATIONS: { if (item_id == ProxyDestinationsMenuItemID::GO_BACK) { - send_main_menu(s, c); + send_main_menu(c); } else if (item_id == ProxyDestinationsMenuItemID::OPTIONS) { - send_menu(c, proxy_options_menu_for_client(s, c)); + send_menu(c, proxy_options_menu_for_client(c)); } else { + auto s = c->require_server_state(); const pair* dest = nullptr; try { dest = &s->proxy_destinations_for_version(c->version()).at(item_id); @@ -1787,7 +1799,7 @@ static void on_10(shared_ptr s, shared_ptr c, // 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)) { - send_ep3_confirm_tournament_entry(s, c, nullptr); + send_ep3_confirm_tournament_entry(c, nullptr); } c->proxy_destination_address = resolve_ipv4(dest->first); @@ -1799,7 +1811,7 @@ static void on_10(shared_ptr s, shared_ptr c, send_update_client_config(c); } else { send_update_client_config(c); - send_client_to_proxy_server(s, c); + send_client_to_proxy_server(c); } } } @@ -1807,6 +1819,7 @@ static void on_10(shared_ptr s, shared_ptr c, } case MenuID::GAME: { + auto s = c->require_server_state(); auto game = s->find_lobby(item_id); if (!game) { send_lobby_message_box(c, u"$C6You cannot join this\ngame because it no\nlonger exists."); @@ -1867,20 +1880,22 @@ static void on_10(shared_ptr s, shared_ptr c, } case MenuID::QUEST_FILTER: { + auto s = c->require_server_state(); if (!s->quest_index) { send_lobby_message_box(c, u"$C6Quests are not available."); break; } - shared_ptr l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr; + shared_ptr l = c->lobby.lock(); auto quests = s->quest_index->filter(c->quest_version(), item_id); // Hack: Assume the menu to be sent is the download quest menu if the // client is not in any lobby - send_quest_menu(c, MenuID::QUEST, quests, !c->lobby_id); + send_quest_menu(c, MenuID::QUEST, quests, !l); break; } case MenuID::QUEST: { + auto s = c->require_server_state(); if (!s->quest_index) { send_lobby_message_box(c, u"$C6Quests are not available."); break; @@ -1893,13 +1908,10 @@ static void on_10(shared_ptr s, shared_ptr c, // If the client is not in a lobby, send the quest as a download quest. // Otherwise, they must be in a game to load a quest. - shared_ptr l; - if (c->lobby_id) { - l = s->find_lobby(c->lobby_id); - if (!l->is_game()) { - send_lobby_message_box(c, u"$C6Quests cannot be\nloaded in lobbies."); - break; - } + auto l = c->lobby.lock(); + if (l && !l->is_game()) { + send_lobby_message_box(c, u"$C6Quests cannot be\nloaded in lobbies."); + break; } bool is_ep3 = (q->episode == Episode::EP3); @@ -1969,13 +1981,14 @@ static void on_10(shared_ptr s, shared_ptr c, case MenuID::PATCHES: if (item_id == PatchesMenuItemID::GO_BACK) { - send_main_menu(s, c); + send_main_menu(c); } else { if (c->flags & 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; send_function_call( c, s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key)); @@ -1986,13 +1999,14 @@ static void on_10(shared_ptr s, shared_ptr c, case MenuID::PROGRAMS: if (item_id == ProgramsMenuItemID::GO_BACK) { - send_main_menu(s, c); + send_main_menu(c); } else { if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { throw runtime_error("client does not support send_function_call"); } + auto s = c->require_server_state(); c->loading_dol_file = s->dol_file_index->item_id_to_file.at(item_id); // Send the first function call, which triggers the process of loading a @@ -2010,10 +2024,10 @@ static void on_10(shared_ptr s, shared_ptr c, if (!(c->flags & Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Episode 3 client attempted to join tournament"); } + auto s = c->require_server_state(); auto tourn = s->ep3_tournament_index->get_tournament(item_id); if (tourn) { - send_ep3_tournament_entry_list(c, tourn, - (menu_id == MenuID::TOURNAMENTS_FOR_SPEC)); + send_ep3_tournament_entry_list(c, tourn, (menu_id == MenuID::TOURNAMENTS_FOR_SPEC)); } break; } @@ -2029,6 +2043,7 @@ static void on_10(shared_ptr s, shared_ptr c, team_name = c->game_data.player()->disp.name; team_name += decode_sjis(string_printf("/%" PRIX32, c->license->serial_number)); } + auto s = c->require_server_state(); uint16_t tourn_num = item_id >> 16; uint16_t team_index = item_id & 0xFFFF; auto tourn = s->ep3_tournament_index->get_tournament(tourn_num); @@ -2038,7 +2053,7 @@ static void on_10(shared_ptr s, shared_ptr c, try { team->register_player(c, encode_sjis(team_name), encode_sjis(password)); c->ep3_tournament_team = team; - tourn->send_all_state_updates(s); + tourn->send_all_state_updates(); string message = string_printf("$C7You are registered in $C6%s$C7.\n\ \n\ After registration ends, start your matches by\n\ @@ -2069,9 +2084,9 @@ lobby warp in the lobby along with your partner\n\ } } -static void on_84(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_84(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); if (cmd.menu_id != MenuID::LOBBY) { send_message_box(c, u"Incorrect menu ID"); @@ -2080,17 +2095,15 @@ static void on_84(shared_ptr s, shared_ptr c, // If the client isn't in any lobby, then they just left a game. Add them to // the lobby they requested, but fall back to another lobby if it's full. - if (c->lobby_id == 0) { + if (!c->lobby.lock()) { c->preferred_lobby_id = cmd.item_id; s->add_client_to_available_lobby(c); // If the client already is in a lobby, then they're using the lobby // teleporter; add them to the lobby they requested or send a failure message. } else { - shared_ptr new_lobby; - try { - new_lobby = s->find_lobby(cmd.item_id); - } catch (const out_of_range&) { + auto new_lobby = s->find_lobby(cmd.item_id); + if (!new_lobby) { send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby does not\nexist."); return; } @@ -2111,20 +2124,18 @@ static void on_84(shared_ptr s, shared_ptr c, } } -static void on_08_E6(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t, const string& data) { +static void on_08_E6(shared_ptr c, uint16_t command, uint32_t, const string& data) { check_size_v(data.size(), 0); - send_game_menu(c, s, (command == 0xE6), false); + send_game_menu(c, (command == 0xE6), false); } -static void on_1F(shared_ptr s, - shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_1F(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); + auto s = c->require_server_state(); send_menu(c, s->information_menu_for_version(c->version()), true); } -static void on_A0(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string&) { +static void on_A0(shared_ptr c, uint16_t, uint32_t, const string&) { // The client sends data in this command, but none of it is important. We // intentionally don't call check_size here, but just ignore the data. @@ -2144,36 +2155,33 @@ static void on_A0(shared_ptr s, shared_ptr c, const auto& port_name = version_to_login_port_name.at(static_cast(c->version())); + auto s = c->require_server_state(); send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port); } -static void on_A1(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +static void on_A1(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { // newserv doesn't have blocks; treat block change the same as ship change - on_A0(s, c, command, flag, data); + on_A0(c, command, flag, data); } -static void on_8E_DCNTE(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +static void on_8E_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { - on_A0(s, c, command, flag, data); + on_A0(c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8E"); } } -static void on_8F_DCNTE(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +static void on_8F_DCNTE(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { if (c->flags & Client::Flag::IS_DC_TRIAL_EDITION) { - on_A1(s, c, command, flag, data); + on_A1(c, command, flag, data); } else { throw runtime_error("non-DCNTE client sent 8F"); } } -static void send_dol_file_chunk(shared_ptr s, shared_ptr c, - uint32_t start_addr) { +static void send_dol_file_chunk(shared_ptr c, uint32_t start_addr) { size_t offset = start_addr - c->dol_base_addr; if (offset >= c->loading_dol_file->data.size()) { throw logic_error("DOL file offset beyond end of data"); @@ -2185,6 +2193,7 @@ static void send_dol_file_chunk(shared_ptr s, shared_ptr c, size_t bytes_to_send = min(0x1000, c->loading_dol_file->data.size() - offset); string data_to_send = c->loading_dol_file->data.substr(offset, bytes_to_send); + auto s = c->require_server_state(); auto fn = s->function_code_index->name_to_function.at("WriteMemory"); unordered_map label_writes( {{"dest_addr", start_addr}, {"size", bytes_to_send}}); @@ -2195,9 +2204,9 @@ static void send_dol_file_chunk(shared_ptr s, shared_ptr c, send_ship_info(c, decode_sjis(info)); } -static void on_B3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t flag, const string& data) { +static void on_B3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); if (!c->function_call_response_queue.empty()) { auto& handler = c->function_call_response_queue.front(); @@ -2207,7 +2216,7 @@ static void on_B3(shared_ptr s, shared_ptr c, auto called_fn = s->function_code_index->index_to_function.at(flag); if (called_fn->name == "ReadMemoryWord") { c->dol_base_addr = (cmd.return_value - c->loading_dol_file->data.size()) & (~3); - send_dol_file_chunk(s, c, c->dol_base_addr); + send_dol_file_chunk(c, c->dol_base_addr); } else if (called_fn->name == "WriteMemory") { if (cmd.return_value >= c->dol_base_addr + c->loading_dol_file->data.size()) { auto fn = s->function_code_index->name_to_function.at("RunDOL"); @@ -2218,7 +2227,7 @@ static void on_B3(shared_ptr s, shared_ptr c, c->should_disconnect = true; } else { - send_dol_file_chunk(s, c, cmd.return_value); + send_dol_file_chunk(c, cmd.return_value); } } else { throw logic_error("unknown function called during DOL loading"); @@ -2228,16 +2237,16 @@ static void on_B3(shared_ptr s, shared_ptr c, } } -static void on_A2(shared_ptr s, shared_ptr c, - uint16_t, uint32_t flag, const string& data) { +static void on_A2(shared_ptr c, uint16_t, uint32_t flag, const string& data) { check_size_v(data.size(), 0); + auto s = c->require_server_state(); if (!s->quest_index) { send_lobby_message_box(c, u"$C6Quests are not available."); return; } - auto l = s->find_lobby(c->lobby_id); + auto l = c->lobby.lock(); if (!l || !l->is_game()) { send_lobby_message_box(c, u"$C6Quests are not available\nin lobbies."); return; @@ -2279,11 +2288,10 @@ static void on_A2(shared_ptr s, shared_ptr c, } } -static void on_AC_V3_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_AC_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); - auto l = s->find_lobby(c->lobby_id); + auto l = c->require_lobby(); if (c->flags & Client::Flag::LOADING_RUNNING_QUEST) { c->flags &= ~Client::Flag::LOADING_RUNNING_QUEST; @@ -2323,8 +2331,7 @@ static void on_AC_V3_BB(shared_ptr s, shared_ptr c, } } -static void on_AA(shared_ptr s, - shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_AA(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); if (c->version() == GameVersion::DC || c->version() == GameVersion::PC) { @@ -2334,8 +2341,8 @@ static void on_AA(shared_ptr s, throw runtime_error("trial edition client sent update quest stats command"); } - auto l = s->find_lobby(c->lobby_id); - if (!l || !l->is_game() || !l->quest.get()) { + auto l = c->require_lobby(); + if (!l->is_game() || !l->quest.get()) { return; } @@ -2345,10 +2352,12 @@ static void on_AA(shared_ptr s, send_command_t(c, 0xAB, 0x00, response); } -static void on_D7_GC(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_D7_GC(shared_ptr c, uint16_t, uint32_t, const string& data) { string filename(data); strip_trailing_zeroes(filename); + if (filename.find('/') != string::npos) { + throw runtime_error("GBA file name includes directory separator"); + } static FileContentsCache gba_file_cache(300 * 1000 * 1000); auto f = gba_file_cache.get_or_load("system/gba/" + filename).file; @@ -2379,21 +2388,17 @@ static void send_file_chunk( } } -static void on_44_A6_V3_BB(shared_ptr, shared_ptr c, - uint16_t command, uint32_t, const string& data) { +static void on_44_A6_V3_BB(shared_ptr c, uint16_t command, uint32_t, const string& data) { const auto& cmd = check_size_t(data); send_file_chunk(c, cmd.filename, 0, (command == 0xA6)); } -static void on_13_A7_V3_BB(shared_ptr, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +static void on_13_A7_V3_BB(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { const auto& cmd = check_size_t(data); send_file_chunk(c, cmd.filename, flag + 1, (command == 0xA7)); } -static void on_61_98(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { - +static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { switch (c->version()) { case GameVersion::DC: { if (c->flags & Client::Flag::IS_DC_V1) { @@ -2446,6 +2451,8 @@ static void on_61_98(shared_ptr s, shared_ptr c, cmd = &check_size_t(data, 0xFFFF); } + auto s = c->require_server_state(); + // We use the flag field in this command to differentiate between Ep3 // Trial Edition and the final version: Trial Edition sends flag=3, and // the final version sends flag=4. Because the contents of the card list @@ -2455,7 +2462,7 @@ static void on_61_98(shared_ptr s, shared_ptr c, if (c->flags & Client::Flag::IS_EPISODE_3) { bool flags_changed = false; if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) { - send_ep3_card_list_update(s, c); + send_ep3_card_list_update(c); c->flags |= Client::Flag::HAS_EP3_CARD_DEFS; flags_changed = true; } @@ -2466,7 +2473,7 @@ static void on_61_98(shared_ptr s, shared_ptr c, flags_changed = true; } } - s->ep3_tournament_index->link_client(s, c); + s->ep3_tournament_index->link_client(c); if (flags_changed) { send_update_client_config(c); } @@ -2515,6 +2522,7 @@ static void on_61_98(shared_ptr s, shared_ptr c, throw logic_error("player data command not implemented for version"); } + auto s = c->require_server_state(); auto player = c->game_data.player(false); if (player) { string name_str = remove_language_marker(encode_sjis(player->disp.name)); @@ -2559,32 +2567,23 @@ static void on_61_98(shared_ptr s, shared_ptr c, // We use 61 during the lobby server init sequence to trigger joining an // available lobby - if (!c->lobby_id && (c->server_behavior == ServerBehavior::LOBBY_SERVER)) { + if (!c->lobby.lock() && (c->server_behavior == ServerBehavior::LOBBY_SERVER)) { s->add_client_to_available_lobby(c); } } } -static void on_6x_C9_CB(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +static void on_6x_C9_CB(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { check_size_v(data.size(), 4, 0xFFFF); - - auto l = s->find_lobby(c->lobby_id); - if (!l) { - return; - } - - on_subcommand_multi(s, l, c, command, flag, data); + on_subcommand_multi(c, command, flag, data); } -static void on_chat_generic( - shared_ptr s, shared_ptr c, const u16string& text) { - +static void on_chat_generic(shared_ptr c, const u16string& text) { if (text.empty()) { return; } - auto l = s->find_lobby(c->lobby_id); + auto l = c->lobby.lock(); if (!l) { return; } @@ -2605,7 +2604,7 @@ static void on_chat_generic( if (processed_text[1] == L'$') { processed_text = processed_text.substr(1); } else { - on_chat_command(s, l, c, processed_text); + on_chat_command(c, processed_text); return; } } @@ -2639,31 +2638,27 @@ static void on_chat_generic( } } -static void on_06_PC_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_06_PC_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data, 0xFFFF); u16string text(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t)); strip_trailing_zeroes(text); - on_chat_generic(s, c, text); + on_chat_generic(c, text); } -static void on_06_DC_V3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_06_DC_V3(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data, 0xFFFF); u16string decoded_s = decode_sjis(cmd.text.dcv3, data.size() - sizeof(C_Chat_06)); - on_chat_generic(s, c, decoded_s); + on_chat_generic(c, decoded_s); } -static void on_00E0_BB(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_00E0_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); send_team_and_key_config_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, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); if (c->bb_game_state == ClientStateBB::CHOOSE_PLAYER) { @@ -2693,8 +2688,7 @@ static void on_00E3_BB(shared_ptr, shared_ptr c, } } -static void on_00E8_BB(shared_ptr, shared_ptr c, - uint16_t command, uint32_t, const string& data) { +static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, const string& data) { constexpr size_t max_count = sizeof(GuildCardFileBB::entries) / sizeof(GuildCardEntryBB); constexpr size_t max_blocked = sizeof(GuildCardFileBB::blocked) / sizeof(GuildCardBB); switch (command) { @@ -2832,16 +2826,14 @@ static void on_00E8_BB(shared_ptr, shared_ptr c, } } -static void on_DC_BB(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_DC_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); if (cmd.cont) { send_guild_card_chunk_bb(c, cmd.chunk_index); } } -static void on_xxEB_BB(shared_ptr, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +static void on_xxEB_BB(shared_ptr c, uint16_t command, uint32_t flag, const string& data) { check_size_v(data.size(), 0); if (command == 0x04EB) { @@ -2853,8 +2845,7 @@ static void on_xxEB_BB(shared_ptr, shared_ptr c, } } -static void on_00EC_BB(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_00EC_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); if (cmd.reason == 2) { c->game_data.account()->newserv_flags |= AccountFlag::IN_DRESSING_ROOM; @@ -2865,8 +2856,7 @@ static void on_00EC_BB(shared_ptr, shared_ptr c, } } -static void on_00E5_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); if (!c->license) { @@ -2901,6 +2891,7 @@ static void on_00E5_BB(shared_ptr s, shared_ptr c, } } else { try { + auto s = c->require_server_state(); c->game_data.create_player(cmd.preview, s->level_table); } catch (const exception& e) { string message = string_printf("$C6New character could not be created:\n%s", e.what()); @@ -2915,8 +2906,7 @@ static void on_00E5_BB(shared_ptr s, shared_ptr c, send_approve_player_choice_bb(c); } -static void on_xxED_BB(shared_ptr, shared_ptr c, - uint16_t command, uint32_t, const string& data) { +static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, const string& data) { const auto* cmd = reinterpret_cast(data.data()); switch (command) { @@ -2953,8 +2943,7 @@ static void on_xxED_BB(shared_ptr, shared_ptr c, } } -static void on_00E7_BB(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); // We only trust the player's quest data and challenge data. @@ -2967,44 +2956,42 @@ static void on_00E7_BB(shared_ptr, shared_ptr c, c->game_data.player()->quest_data2 = cmd.quest_data2; } -static void on_00E2_BB(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_00E2_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { // Some clients have only a uint32_t at the end for team rewards auto& cmd = check_size_t(data, sizeof(KeyAndTeamConfigBB) - 4, sizeof(KeyAndTeamConfigBB)); c->game_data.account()->key_config = cmd; } -static void on_89(shared_ptr s, shared_ptr c, - uint16_t, uint32_t flag, const string& data) { +static void on_89(shared_ptr c, uint16_t, uint32_t flag, const string& data) { check_size_v(data.size(), 0); c->lobby_arrow_color = flag; - auto l = s->find_lobby(c->lobby_id); + auto l = c->lobby.lock(); if (l) { send_arrow_update(l); } } -static void on_40(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_40(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); try { + auto s = c->require_server_state(); auto result = s->find_client(nullptr, cmd.target_guild_card_number); - auto result_lobby = s->find_lobby(result->lobby_id); - send_card_search_result(s, c, result, result_lobby); + auto result_lobby = result->lobby.lock(); + if (result_lobby) { + send_card_search_result(c, result, result_lobby); + } } catch (const out_of_range&) { } } -static void on_C0(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string&) { +static void on_C0(shared_ptr c, uint16_t, uint32_t, const string&) { // TODO: Implement choice search. send_text_message(c, u"$C6Choice Search is\nnot supported"); } -static void on_81(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_81(shared_ptr c, uint16_t, uint32_t, const string& data) { u16string message; uint32_t to_guild_card_number; switch (c->version()) { @@ -3034,7 +3021,7 @@ static void on_81(shared_ptr s, shared_ptr c, shared_ptr target; try { - target = s->find_client(nullptr, to_guild_card_number); + target = c->require_server_state()->find_client(nullptr, to_guild_card_number); } catch (const out_of_range&) { } @@ -3069,56 +3056,47 @@ static void on_81(shared_ptr s, shared_ptr c, } } -static void on_D8(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_D8(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); - send_info_board(c, s->find_lobby(c->lobby_id)); + send_info_board(c); } template -void on_D9_t(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +void on_D9_t(shared_ptr c, const string& data) { check_size_v(data.size(), 0, c->game_data.player()->info_board.size() * sizeof(CharT)); c->game_data.player()->info_board.assign( reinterpret_cast(data.data()), data.size() / sizeof(CharT)); } -void on_D9_a(shared_ptr s, shared_ptr c, - uint16_t cmd, uint32_t flag, const string& data) { - on_D9_t(s, c, cmd, flag, data); +void on_D9_a(shared_ptr c, uint16_t, uint32_t, const string& data) { + on_D9_t(c, data); } -void on_D9_w(shared_ptr s, shared_ptr c, - uint16_t cmd, uint32_t flag, const string& data) { - on_D9_t(s, c, cmd, flag, data); +void on_D9_w(shared_ptr c, uint16_t, uint32_t, const string& data) { + on_D9_t(c, data); } template -void on_C7_t(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +void on_C7_t(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0, c->game_data.player()->auto_reply.size() * sizeof(CharT)); c->game_data.player()->auto_reply.assign( reinterpret_cast(data.data()), data.size() / sizeof(CharT)); } -void on_C7_a(shared_ptr s, shared_ptr c, - uint16_t cmd, uint32_t flag, const string& data) { - on_C7_t(s, c, cmd, flag, data); +void on_C7_a(shared_ptr c, uint16_t cmd, uint32_t flag, const string& data) { + on_C7_t(c, cmd, flag, data); } -void on_C7_w(shared_ptr s, shared_ptr c, - uint16_t cmd, uint32_t flag, const string& data) { - on_C7_t(s, c, cmd, flag, data); +void on_C7_w(shared_ptr c, uint16_t cmd, uint32_t flag, const string& data) { + on_C7_t(c, cmd, flag, data); } -static void on_C8(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_C8(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); c->game_data.player()->auto_reply.clear(0); } -static void on_C6(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_C6(shared_ptr c, uint16_t, uint32_t, const string& data) { if (c->version() == GameVersion::BB) { const auto& cmd = check_size_t(data); c->game_data.account()->blocked_senders = cmd.blocked_senders; @@ -3151,10 +3129,7 @@ shared_ptr create_game_generic( throw invalid_argument("incorrect difficulty level"); } - auto current_lobby = s->find_lobby(c->lobby_id); - if (!current_lobby) { - throw invalid_argument("cannot make a game from outside any lobby"); - } + auto current_lobby = c->require_lobby(); uint8_t min_level; // A player's actual level is their displayed level - 1, so the minimums for @@ -3315,9 +3290,9 @@ shared_ptr create_game_generic( return game; } -static void on_C1_PC(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_C1_PC(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); uint32_t flags = Lobby::Flag::NON_V1_ONLY; GameMode mode = GameMode::NORMAL; @@ -3334,8 +3309,8 @@ static void on_C1_PC(shared_ptr s, shared_ptr c, } } -static void on_0C_C1_E7_EC(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t, const string& data) { +static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, const string& data) { + 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))) { @@ -3390,6 +3365,10 @@ static void on_0C_C1_E7_EC(shared_ptr s, shared_ptr c, throw runtime_error("incorrect menu ID"); } watched_lobby = s->find_lobby(cmd.item_id); + if (!watched_lobby) { + send_lobby_message_box(c, u"$C6This game no longer\nexists"); + return; + } if (watched_lobby->flags & Lobby::Flag::SPECTATORS_FORBIDDEN) { send_lobby_message_box(c, u"$C6This game does not\nallow spectators"); return; @@ -3398,8 +3377,7 @@ static void on_0C_C1_E7_EC(shared_ptr s, shared_ptr c, } game = create_game_generic( - s, c, name.c_str(), password.c_str(), episode, mode, cmd.difficulty, - flags, watched_lobby); + s, c, name.c_str(), password.c_str(), episode, mode, cmd.difficulty, flags, watched_lobby); if (game && (game->episode == Episode::EP3)) { game->ep3_ex_result_values = s->ep3_default_ex_values; } @@ -3411,9 +3389,9 @@ static void on_0C_C1_E7_EC(shared_ptr s, shared_ptr c, } } -static void on_C1_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_C1_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); uint32_t flags = Lobby::Flag::NON_V1_ONLY; GameMode mode = GameMode::NORMAL; @@ -3450,8 +3428,7 @@ static void on_C1_BB(shared_ptr s, shared_ptr c, } } -static void on_8A(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_8A(shared_ptr c, uint16_t, uint32_t, const string& data) { if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_DC_TRIAL_EDITION)) { const auto& cmd = check_size_t(data); set_console_client_flags(c, cmd.sub_version); @@ -3459,20 +3436,16 @@ static void on_8A(shared_ptr s, shared_ptr c, } else { check_size_v(data.size(), 0); - auto l = s->find_lobby(c->lobby_id); - if (!l) { - throw invalid_argument("client not in any lobby"); - } + auto l = c->require_lobby(); send_lobby_name(c, l->name.c_str()); } } -static void on_6F(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_6F(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); - auto l = s->find_lobby(c->lobby_id); - if (!l || !l->is_game()) { + auto l = c->require_lobby(); + if (!l->is_game()) { throw runtime_error("client sent ready command outside of game"); } c->flags &= (~Client::Flag::LOADING); @@ -3520,11 +3493,10 @@ static void on_6F(shared_ptr s, shared_ptr c, // If there are more players to bring in, try to do so c->disconnect_hooks.erase(ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME); - add_next_game_client(s, l); + add_next_game_client(l); } -static void on_D0_V3_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_D0_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); if (c->game_data.pending_item_trade) { @@ -3534,8 +3506,8 @@ static void on_D0_V3_BB(shared_ptr s, shared_ptr c, throw runtime_error("invalid item count in trade items command"); } - auto l = s->find_lobby(c->lobby_id); - if (!l || !l->is_game()) { + auto l = c->require_lobby(); + if (!l->is_game()) { throw runtime_error("trade command received in non-game lobby"); } auto target_c = l->clients.at(cmd.target_client_id); @@ -3566,16 +3538,15 @@ static void on_D0_V3_BB(shared_ptr s, shared_ptr c, } } -static void on_D2_V3_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_D2_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); if (!c->game_data.pending_item_trade) { throw runtime_error("player executed a trade with none pending"); } - auto l = s->find_lobby(c->lobby_id); - if (!l || !l->is_game()) { + auto l = c->require_lobby(); + if (!l->is_game()) { throw runtime_error("trade command received in non-game lobby"); } auto target_c = l->clients.at(c->game_data.pending_item_trade->other_client_id); @@ -3597,8 +3568,7 @@ static void on_D2_V3_BB(shared_ptr s, shared_ptr c, } } -static void on_D4_V3_BB(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_D4_V3_BB(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); // Annoyingly, if the other client disconnects at a certain point during the @@ -3613,8 +3583,8 @@ static void on_D4_V3_BB(shared_ptr s, shared_ptr c, send_command(c, 0xD4, 0x00); // Cancel the other side of the trade too, if it's open - auto l = s->find_lobby(c->lobby_id); - if (!l || !l->is_game()) { + auto l = c->require_lobby(); + if (!l->is_game()) { throw runtime_error("trade command received in non-game lobby"); } auto target_c = l->clients.at(other_client_id); @@ -3628,12 +3598,11 @@ static void on_D4_V3_BB(shared_ptr s, shared_ptr c, send_command(target_c, 0xD4, 0x00); } -static void on_EE_Ep3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t flag, const string& data) { +static void on_EE_Ep3(shared_ptr c, uint16_t, uint32_t flag, const string& data) { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Ep3 client sent card trade command"); } - auto l = s->find_lobby(c->lobby_id); + auto l = c->require_lobby(); if (!l->is_game() || !l->is_ep3()) { throw runtime_error("client sent card trade command outside of Ep3 game"); } @@ -3731,23 +3700,21 @@ static void on_EE_Ep3(shared_ptr s, shared_ptr c, } } -static void on_EF_Ep3(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_EF_Ep3(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); if (!(c->flags & Client::Flag::IS_EPISODE_3)) { throw runtime_error("non-Ep3 client sent card auction request"); } - auto l = s->find_lobby(c->lobby_id); + auto l = c->require_lobby(); if (!l->is_game() || !l->is_ep3()) { throw runtime_error("client sent card auction request outside of Ep3 game"); } - send_ep3_card_auction(s, l); + send_ep3_card_auction(l); } -static void on_xxEA_BB(shared_ptr, shared_ptr c, - uint16_t command, uint32_t, const string&) { +static void on_xxEA_BB(shared_ptr c, uint16_t command, uint32_t, const string&) { // TODO: Implement teams. This command has a very large number of subcommands // (up to 20EA!). @@ -3760,8 +3727,7 @@ static void on_xxEA_BB(shared_ptr, shared_ptr c, } } -static void on_02_P(shared_ptr, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_02_P(shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); send_command(c, 0x04, 0x00); // This requests the user's login information } @@ -3797,9 +3763,9 @@ static void change_to_directory_patch( } } -static void on_04_P(shared_ptr s, shared_ptr c, - uint16_t, uint32_t, const string& data) { +static void on_04_P(shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); + auto s = c->require_server_state(); try { auto l = s->license_manager->verify_bb(cmd.username, cmd.password); @@ -3864,8 +3830,7 @@ static void on_04_P(shared_ptr s, shared_ptr c, } } -static void on_0F_P(shared_ptr, - shared_ptr c, uint16_t, uint32_t, const string& data) { +static void on_0F_P(shared_ptr c, uint16_t, uint32_t, const string& data) { auto& cmd = check_size_t(data); auto& req = c->patch_file_checksum_requests.at(cmd.request_id); req.crc32 = cmd.checksum; @@ -3873,8 +3838,7 @@ static void on_0F_P(shared_ptr, req.response_received = true; } -static void on_10_P(shared_ptr, - shared_ptr c, uint16_t, uint32_t, const string&) { +static void on_10_P(shared_ptr c, uint16_t, uint32_t, const string&) { S_StartFileDownloads_Patch_11 start_cmd = {0, 0}; for (const auto& req : c->patch_file_checksum_requests) { @@ -3906,18 +3870,16 @@ static void on_10_P(shared_ptr, send_command(c, 0x12, 0x00); } -static void on_ignored(shared_ptr, shared_ptr, - uint16_t, uint32_t, const string&) {} +static void on_ignored(shared_ptr, uint16_t, uint32_t, const string&) {} -static void on_unimplemented_command(shared_ptr, +static void on_unimplemented_command( shared_ptr c, uint16_t command, uint32_t flag, const string& data) { c->log.warning("Unknown command: size=%04zX command=%04hX flag=%08" PRIX32, data.size(), command, flag); throw invalid_argument("unimplemented command"); } -typedef void (*on_command_t)(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data); +typedef void (*on_command_t)(shared_ptr c, uint16_t command, uint32_t flag, const string& data); // Command handler table, indexed by command number and game version. Null // entries in this table cause on_unimplemented_command to be called, which @@ -4233,14 +4195,19 @@ static void check_unlicensed_command(GameVersion version, uint8_t command) { } } -void on_command(shared_ptr s, shared_ptr c, - uint16_t command, uint32_t flag, const string& data) { +void on_command( + shared_ptr c, + uint16_t command, + uint32_t flag, + const string& data) { string encoded_name; auto player = c->game_data.player(false); if (player) { encoded_name = remove_language_marker(encode_sjis(player->disp.name)); } + c->reschedule_ping_and_timeout_events(); + // Most of the command handlers assume the client is registered, logged in, // and not banned (and therefore that c->license is not null), so the client // is allowed to access normal functionality. This check prevents clients from @@ -4251,31 +4218,30 @@ void on_command(shared_ptr s, shared_ptr c, auto fn = handlers[command & 0xFF][static_cast(c->version())]; if (fn) { - fn(s, c, command, flag, data); + fn(c, command, flag, data); } else { - on_unimplemented_command(s, c, command, flag, data); + on_unimplemented_command(c, command, flag, data); } } -void on_command_with_header(shared_ptr s, shared_ptr c, - string& data) { +void on_command_with_header(shared_ptr c, string& data) { switch (c->version()) { case GameVersion::DC: case GameVersion::GC: case GameVersion::XB: { auto& header = check_size_t(data, 0xFFFF); - on_command(s, c, header.command, header.flag, data.substr(sizeof(header))); + on_command(c, header.command, header.flag, data.substr(sizeof(header))); break; } case GameVersion::PC: case GameVersion::PATCH: { auto& header = check_size_t(data, 0xFFFF); - on_command(s, c, header.command, header.flag, data.substr(sizeof(header))); + on_command(c, header.command, header.flag, data.substr(sizeof(header))); break; } case GameVersion::BB: { auto& header = check_size_t(data, 0xFFFF); - on_command(s, c, header.command, header.flag, data.substr(sizeof(header))); + on_command(c, header.command, header.flag, data.substr(sizeof(header))); break; } default: diff --git a/src/ReceiveCommands.hh b/src/ReceiveCommands.hh index d1ad0472..981decac 100644 --- a/src/ReceiveCommands.hh +++ b/src/ReceiveCommands.hh @@ -16,10 +16,7 @@ std::shared_ptr create_game_generic( std::shared_ptr watched_lobby = nullptr, std::shared_ptr battle_player = nullptr); -void on_connect(std::shared_ptr s, std::shared_ptr c); -void on_disconnect(std::shared_ptr s, - std::shared_ptr c); -void on_command(std::shared_ptr s, std::shared_ptr c, - uint16_t command, uint32_t flag, const std::string& data); -void on_command_with_header(std::shared_ptr s, - std::shared_ptr c, std::string& data); +void on_connect(std::shared_ptr c); +void on_disconnect(std::shared_ptr c); +void on_command(std::shared_ptr c, uint16_t command, uint32_t flag, const std::string& data); +void on_command_with_header(std::shared_ptr c, std::string& data); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index cf02cb10..3f5d8f4d 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -35,7 +35,6 @@ static const unordered_set watcher_subcommands({ }); static void forward_subcommand( - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, @@ -45,9 +44,10 @@ 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)) { - return; + throw runtime_error("Episode 3 command sent by non-Episode 3 client"); } + auto l = c->require_lobby(); if (command_is_private(command)) { if (flag >= l->max_clients) { return; @@ -66,7 +66,6 @@ static void forward_subcommand( } send_command(target, command, flag, data, size); } - } else { send_command_excluding_client(l, c, command, flag, data, size); } @@ -80,7 +79,11 @@ static void forward_subcommand( if ((l->ep3_server && (l->ep3_server->setup_phase != Episode3::SetupPhase::REGISTRATION)) || watcher_subcommands.count(subcommand)) { for (const auto& watcher_lobby : l->watcher_lobbies) { - forward_subcommand(watcher_lobby, c, command, flag, data, size); + for (auto& target : watcher_lobby->clients) { + if (target && (target->flags & Client::Flag::IS_EPISODE_3)) { + send_command(target, command, flag, data, size); + } + } } } @@ -93,9 +96,7 @@ static void forward_subcommand( } } -static void on_invalid(shared_ptr, - shared_ptr, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_invalid(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, 0xFFFF); if (command_is_private(command)) { c->log.error("Invalid subcommand: %02hhX (private to %hhu)", @@ -105,9 +106,7 @@ static void on_invalid(shared_ptr, } } -static void on_unimplemented(shared_ptr, - shared_ptr, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_unimplemented(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, 0xFFFF); if (command_is_private(command)) { c->log.warning("Unknown subcommand: %02hhX (private to %hhu)", @@ -120,25 +119,21 @@ static void on_unimplemented(shared_ptr, } } -static void on_forward_check_size(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_size(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { check_size_t(data, size, 0xFFFF); - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_forward_check_game(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_game(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game()) { return; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_forward_sync_joining_player_state(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_sync_joining_player_state(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game() || !l->any_client_loading()) { return; } @@ -155,12 +150,11 @@ static void on_forward_sync_joining_player_state(shared_ptr, print_data(stderr, decompressed); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_sync_joining_player_item_state(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_sync_joining_player_item_state(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game() || !l->any_client_loading()) { return; } @@ -176,7 +170,7 @@ static void on_sync_joining_player_item_state(shared_ptr, // PSO GC bool sender_is_gc = (c->version() == GameVersion::GC); if (!sender_is_gc && (c->version() != GameVersion::XB)) { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } else { if (flag >= l->max_clients) { @@ -249,9 +243,8 @@ static void on_sync_joining_player_item_state(shared_ptr, } } -static void on_sync_joining_player_disp_and_inventory(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_sync_joining_player_disp_and_inventory(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game() || !l->any_client_loading()) { return; } @@ -267,7 +260,7 @@ static void on_sync_joining_player_disp_and_inventory(shared_ptr, // PSO GC bool sender_is_gc = (c->version() == GameVersion::GC); if (!sender_is_gc && (c->version() != GameVersion::XB)) { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } else { if (flag >= l->max_clients) { @@ -291,82 +284,81 @@ static void on_sync_joining_player_disp_and_inventory(shared_ptr, } } -static void on_forward_check_game_loading(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_game_loading(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game() || !l->any_client_loading()) { return; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_forward_check_size_client(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_size_client(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, 0xFFFF); if (cmd.client_id != c->lobby_client_id) { return; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_forward_check_size_game(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_size_game(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { check_size_t(data, size, 0xFFFF); + auto l = c->require_lobby(); if (!l->is_game()) { return; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_forward_check_size_ep3_lobby(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_size_ep3_lobby(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { check_size_t(data, size, 0xFFFF); + auto l = c->require_lobby(); if (l->is_game() || !l->is_ep3()) { return; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_forward_check_size_ep3_game(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_forward_check_size_ep3_game(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { check_size_t(data, size, 0xFFFF); + auto l = c->require_lobby(); if (!l->is_game() || !l->is_ep3()) { return; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } //////////////////////////////////////////////////////////////////////////////// // Ep3 subcommands -static void on_ep3_sound_chat(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_ep3_sound_chat(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { // Unlike the 6x and C9 commands, subcommands sent with the CB command are - // forwarded from spectator teams to the primary team. The client only uses this - // behavior for the 6xBE command (sound chat), and newserv enforces this rule. + // 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)) { 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)) { auto watched_lobby = l->watched_lobby.lock(); if (watched_lobby) { - forward_subcommand(watched_lobby, c, command, flag, data, size); + for (auto& target : watched_lobby->clients) { + if (target && (target->flags & Client::Flag::IS_EPISODE_3)) { + send_command(target, command, flag, data, size); + } + } } } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_ep3_battle_subs(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* orig_data, size_t size) { +static void on_ep3_battle_subs(shared_ptr c, uint8_t command, uint8_t flag, const void* orig_data, size_t size) { const auto& header = check_size_t(orig_data, size, 0xFFFF); + + auto s = c->require_server_state(); + auto l = c->require_lobby(); if (!l->is_game() || !l->is_ep3()) { return; } @@ -393,17 +385,15 @@ static void on_ep3_battle_subs(shared_ptr s, set_mask_for_ep3_game_command(data.data(), data.size(), mask_key); } - forward_subcommand(l, c, command, flag, data.data(), data.size()); + forward_subcommand(c, command, flag, data.data(), data.size()); } //////////////////////////////////////////////////////////////////////////////// // Chat commands and the like -static void on_send_guild_card(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { - if (!command_is_private(command) || !l || (flag >= l->max_clients) || - (!l->clients[flag])) { +static void on_send_guild_card(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); + if (!command_is_private(command) || (flag >= l->max_clients) || (!l->clients[flag])) { return; } @@ -435,45 +425,30 @@ static void on_send_guild_card(shared_ptr, send_guild_card(l->clients[flag], c); } -// client sends a symbol chat -static void on_symbol_chat(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_symbol_chat(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); - - if (!c->can_chat || (cmd.client_id != c->lobby_client_id)) { - return; + if (c->can_chat && (cmd.client_id == c->lobby_client_id)) { + forward_subcommand(c, command, flag, data, size); } - forward_subcommand(l, c, command, flag, data, size); } -// client sends a word select chat -static void on_word_select(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_word_select(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); - - if (!c->can_chat || (cmd.header.client_id != c->lobby_client_id)) { - return; + if (c->can_chat && (cmd.header.client_id == c->lobby_client_id)) { + forward_subcommand(c, command, flag, data, size); } - - forward_subcommand(l, c, command, flag, data, size); } -// client is done loading into a lobby (we use this to trigger arrow updates) -static void on_set_player_visibility(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_set_player_visibility(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); - if (cmd.header.client_id != c->lobby_client_id) { - return; - } + if (cmd.header.client_id == c->lobby_client_id) { + auto l = c->require_lobby(); - forward_subcommand(l, c, command, flag, data, size); - - if (!l->is_game() && !(c->flags & Client::Flag::IS_DC_V1)) { - send_arrow_update(l); + forward_subcommand(c, command, flag, data, size); + if (!l->is_game() && !(c->flags & Client::Flag::IS_DC_V1)) { + send_arrow_update(l); + } } } @@ -481,19 +456,17 @@ static void on_set_player_visibility(shared_ptr, // Game commands used by cheat mechanisms template -static void on_change_area(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_change_area(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); c->area = cmd.area; - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } // When a player dies, decrease their mag's synchro -static void on_player_died(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_player_died(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, 0xFFFF); + + auto l = c->require_lobby(); if (!l->is_game() || (cmd.client_id != c->lobby_client_id)) { return; } @@ -508,81 +481,75 @@ static void on_player_died(shared_ptr, } } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } // When a player is hit by an enemy, heal them if infinite HP is enabled -static void on_hit_by_enemy(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_hit_by_enemy(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, 0xFFFF); - if (!l->is_game() || (cmd.client_id != c->lobby_client_id)) { - return; - } - forward_subcommand(l, c, command, flag, data, size); - if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->options.infinite_hp) { - send_player_stats_change(l, c, PlayerStatsChange::ADD_HP, 2550); + + 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) { + send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550); + } } } // When a player casts a tech, restore TP if infinite TP is enabled -static void on_cast_technique_finished(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_cast_technique_finished(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); - if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) { - return; - } - forward_subcommand(l, c, command, flag, data, size); - if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->options.infinite_tp) { - send_player_stats_change(l, c, PlayerStatsChange::ADD_TP, 255); + + 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) { + send_player_stats_change(c, PlayerStatsChange::ADD_TP, 255); + } } } -static void on_attack_finished(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_attack_finished(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, offsetof(G_AttackFinished_6x46, targets), sizeof(G_AttackFinished_6x46)); size_t allowed_count = min(cmd.header.size - 2, 11); if (cmd.count > allowed_count) { throw runtime_error("invalid attack finished command"); } - on_forward_check_size_client(s, l, c, command, flag, data, size); + on_forward_check_size_client(c, command, flag, data, size); } -static void on_cast_technique(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_cast_technique(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, offsetof(G_CastTechnique_6x47, targets), sizeof(G_CastTechnique_6x47)); size_t allowed_count = min(cmd.header.size - 2, 10); if (cmd.target_count > allowed_count) { throw runtime_error("invalid cast technique command"); } - on_forward_check_size_client(s, l, c, command, flag, data, size); + on_forward_check_size_client(c, command, flag, data, size); } -static void on_subtract_pb_energy(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_subtract_pb_energy(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size, offsetof(G_SubtractPBEnergy_6x49, entries), sizeof(G_SubtractPBEnergy_6x49)); size_t allowed_count = min(cmd.header.size - 3, 14); if (cmd.entry_count > allowed_count) { throw runtime_error("invalid subtract PB energy command"); } - on_forward_check_size_client(s, l, c, command, flag, data, size); + on_forward_check_size_client(c, command, flag, data, size); } -static void on_switch_state_changed(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_switch_state_changed(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto& cmd = check_size_t(data, size); + + auto l = c->require_lobby(); if (!l->is_game()) { return; } - forward_subcommand(l, c, command, flag, data, size); + + 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 && (c->last_switch_enabled_command.header.subcommand == 0x05)) { @@ -590,8 +557,7 @@ static void on_switch_state_changed(shared_ptr, if (c->options.debug) { send_text_message(c, u"$C5Switch assist"); } - forward_subcommand(l, c, command, flag, &c->last_switch_enabled_command, - sizeof(c->last_switch_enabled_command)); + forward_subcommand(c, command, flag, &c->last_switch_enabled_command, sizeof(c->last_switch_enabled_command)); send_command_t(c, command, flag, c->last_switch_enabled_command); } c->last_switch_enabled_command = cmd; @@ -601,9 +567,7 @@ static void on_switch_state_changed(shared_ptr, //////////////////////////////////////////////////////////////////////////////// template -void on_movement(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +void on_movement(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); if (cmd.header.client_id != c->lobby_client_id) { return; @@ -612,13 +576,11 @@ void on_movement(shared_ptr, c->x = cmd.x; c->z = cmd.z; - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } template -void on_movement_with_area(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +void on_movement_with_area(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); if (cmd.header.client_id != c->lobby_client_id) { return; @@ -628,21 +590,20 @@ void on_movement_with_area(shared_ptr, c->z = cmd.z; c->area = cmd.area; - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } //////////////////////////////////////////////////////////////////////////////// // Item commands -static void on_player_drop_item(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_player_drop_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); if ((cmd.header.client_id != c->lobby_client_id)) { return; } + auto l = c->require_lobby(); if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { auto item = c->game_data.player()->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB); l->add_item(item, cmd.area, cmd.x, cmd.z); @@ -659,18 +620,18 @@ static void on_player_drop_item(shared_ptr, c->game_data.player()->print_inventory(stderr); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } template -void forward_subcommand_with_mag_bswap_t( - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const CmdT& cmd) { +void forward_subcommand_with_mag_bswap_t(shared_ptr c, uint8_t command, uint8_t flag, const CmdT& cmd) { // I'm lazy and this should never happen for item commands (since all players // need to stay in sync) if (command_is_private(command)) { throw runtime_error("6x2B sent via private command"); } + auto l = c->require_lobby(); for (auto& other_c : l->clients) { if (!other_c || other_c == c) { continue; @@ -684,9 +645,7 @@ void forward_subcommand_with_mag_bswap_t( } template -static void on_create_inventory_item_t(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_create_inventory_item_t(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); if ((cmd.header.client_id != c->lobby_client_id)) { @@ -698,6 +657,7 @@ static void on_create_inventory_item_t(shared_ptr, return; } + auto l = c->require_lobby(); if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { PlayerInventoryItem item; item.present = 1; @@ -719,28 +679,25 @@ static void on_create_inventory_item_t(shared_ptr, c->game_data.player()->print_inventory(stderr); } - forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd); + forward_subcommand_with_mag_bswap_t(c, command, flag, cmd); } -static void on_create_inventory_item(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_create_inventory_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { if (size == sizeof(G_CreateInventoryItem_PC_V3_BB_6x2B)) { - on_create_inventory_item_t(s, l, c, command, flag, data, size); + on_create_inventory_item_t(c, command, flag, data, size); } else if (size == sizeof(G_CreateInventoryItem_DC_6x2B)) { - on_create_inventory_item_t(s, l, c, command, flag, data, size); + on_create_inventory_item_t(c, command, flag, data, size); } else { throw runtime_error("invalid size for 6x2B command"); } } template -static void on_drop_partial_stack_t(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_drop_partial_stack_t(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); // TODO: Should we check the client ID here too? + auto l = c->require_lobby(); if (!l->is_game()) { return; } @@ -772,24 +729,21 @@ static void on_drop_partial_stack_t(shared_ptr, c->game_data.player()->print_inventory(stderr); } - forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd); + forward_subcommand_with_mag_bswap_t(c, command, flag, cmd); } -static void on_drop_partial_stack(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_drop_partial_stack(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { if (size == sizeof(G_DropStackedItem_PC_V3_BB_6x5D)) { - on_drop_partial_stack_t(s, l, c, command, flag, data, size); + on_drop_partial_stack_t(c, command, flag, data, size); } else if (size == sizeof(G_DropStackedItem_DC_6x5D)) { - on_drop_partial_stack_t(s, l, c, command, flag, data, size); + on_drop_partial_stack_t(c, command, flag, data, size); } else { throw runtime_error("invalid size for 6x5D command"); } } -static void on_drop_partial_stack_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_drop_partial_stack_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); @@ -831,15 +785,15 @@ static void on_drop_partial_stack_bb(shared_ptr, send_drop_stacked_item(l, item.data, cmd.area, cmd.x, cmd.z); } else { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_buy_shop_item(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_buy_shop_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); + auto s = c->require_server_state(); + auto l = c->require_lobby(); if (!l->is_game() || (cmd.header.client_id != c->lobby_client_id)) { return; } @@ -872,15 +826,14 @@ static void on_buy_shop_item(shared_ptr s, p->print_inventory(stderr); } - forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd); + forward_subcommand_with_mag_bswap_t(c, command, flag, cmd); } template -static void on_box_or_enemy_item_drop_t(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_box_or_enemy_item_drop_t(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); + auto l = c->require_lobby(); if (!l->is_game() || (c->lobby_client_id != l->leader_id)) { return; } @@ -908,26 +861,23 @@ static void on_box_or_enemy_item_drop_t(shared_ptr, } } - forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd); + forward_subcommand_with_mag_bswap_t(c, command, flag, cmd); } -static void on_box_or_enemy_item_drop(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_box_or_enemy_item_drop(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { if (size == sizeof(G_DropItem_DC_6x5F)) { - on_box_or_enemy_item_drop_t(s, l, c, command, flag, data, size); + on_box_or_enemy_item_drop_t(c, command, flag, data, size); } else if (size == sizeof(G_DropItem_PC_V3_BB_6x5F)) { - on_box_or_enemy_item_drop_t(s, l, c, command, flag, data, size); + on_box_or_enemy_item_drop_t(c, command, flag, data, size); } else { throw runtime_error("invalid size for 6x5F command"); } } -static void on_pick_up_item(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_pick_up_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { auto& cmd = check_size_t(data, size); + auto l = c->require_lobby(); if (!l->is_game()) { return; } @@ -956,13 +906,12 @@ static void on_pick_up_item(shared_ptr, effective_c->game_data.player()->print_inventory(stderr); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_pick_up_item_request(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_pick_up_item_request(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { // This is handled by the server on BB, and by the leader on other versions + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { auto& cmd = check_size_t(data, size); @@ -987,22 +936,21 @@ static void on_pick_up_item_request(shared_ptr, } c->game_data.player()->print_inventory(stderr); - send_pick_up_item(l, c, cmd.item_id, cmd.area); + send_pick_up_item(c, cmd.item_id, cmd.area); } else { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_equip_unequip_item(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_equip_unequip_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); if (cmd.header.client_id != c->lobby_client_id) { return; } + auto l = c->require_lobby(); if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { size_t index = c->game_data.player()->inventory.find_item(cmd.item_id); if (cmd.header.subcommand == 0x25) { // Equip @@ -1014,12 +962,10 @@ static void on_equip_unequip_item(shared_ptr, throw logic_error("item tracking not enabled in BB game"); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } static void on_use_item( - shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, @@ -1030,6 +976,7 @@ static void on_use_item( return; } + auto l = c->require_lobby(); if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { size_t index = c->game_data.player()->inventory.find_item(cmd.item_id); string name, colored_name; @@ -1040,7 +987,7 @@ static void on_use_item( name = item.name(false); colored_name = item.name(true); } - player_use_item(s, c, index); + player_use_item(c, index); l->log.info("Player used item %hu:%08" PRIX32 " (%s)", cmd.header.client_id.load(), cmd.item_id.load(), name.c_str()); @@ -1051,12 +998,10 @@ static void on_use_item( c->game_data.player()->print_inventory(stderr); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } static void on_feed_mag( - shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, @@ -1067,6 +1012,7 @@ static void on_feed_mag( return; } + auto l = c->require_lobby(); if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { size_t mag_index = c->game_data.player()->inventory.find_item(cmd.mag_item_id); size_t fed_index = c->game_data.player()->inventory.find_item(cmd.fed_item_id); @@ -1081,7 +1027,7 @@ static void on_feed_mag( mag_name = mag_item.name(false); mag_colored_name = mag_item.name(true); } - player_feed_mag(s, c, mag_index, fed_index); + player_feed_mag(c, mag_index, fed_index); // On BB, the player only sends a 6x28; on other versions, the player sends // a 6x29 immediately after to destroy the fed item. So on BB, we should @@ -1102,14 +1048,13 @@ static void on_feed_mag( c->game_data.player()->print_inventory(stderr); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->is_ep3()) { - on_ep3_battle_subs(s, l, c, command, flag, data, size); + on_ep3_battle_subs(c, command, flag, data, size); } else if (!l->item_creator.get()) { throw runtime_error("received shop subcommand without item creator present"); @@ -1121,6 +1066,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr s, throw logic_error("item creator missing from BB game"); } + auto s = c->require_server_state(); size_t level = c->game_data.player()->disp.stats.level + 1; switch (cmd.shop_type) { case 0: @@ -1145,17 +1091,17 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr s, } } -static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { +static void on_open_bank_bb_or_card_trade_counter_ep3(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if ((l->version == GameVersion::BB) && l->is_game()) { send_bank(c); } else if ((l->version == GameVersion::GC) && l->is_ep3()) { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_ep3_private_word_select_bb_bank_action(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { +static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); @@ -1181,7 +1127,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr, auto item = c->game_data.player()->remove_item( cmd.item_id, cmd.item_amount, c->version() != GameVersion::BB); c->game_data.player()->bank.add_item(item); - send_destroy_item(l, c, cmd.item_id, cmd.item_amount); + send_destroy_item(c, cmd.item_id, cmd.item_amount); } } else if (cmd.action == 1) { // take if (cmd.item_id == 0xFFFFFFFF) { // meseta @@ -1198,18 +1144,17 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr, PlayerInventoryItem item = bank_item; item.data.id = l->generate_item_id(0xFF); c->game_data.player()->add_item(item); - send_create_inventory_item(l, c, item.data); + send_create_inventory_item(c, item.data); } } } else if ((c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3)) { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_sort_inventory_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, - const void* data, size_t size) { +static void on_sort_inventory_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); @@ -1239,9 +1184,8 @@ static void on_sort_inventory_bb(shared_ptr, //////////////////////////////////////////////////////////////////////////////// // EXP/Drop Item commands -static void on_entity_drop_item_request( - shared_ptr, shared_ptr l, shared_ptr c, - uint8_t command, uint8_t flag, const void* data, size_t size) { +static void on_entity_drop_item_request(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game()) { return; } @@ -1250,7 +1194,7 @@ static void on_entity_drop_item_request( // enabled, or just ignore it) instead of generating the item drop command if (l->version != GameVersion::BB) { if (l->flags & Lobby::Flag::DROPS_ENABLED) { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } return; } @@ -1301,9 +1245,8 @@ static void on_entity_drop_item_request( send_drop_item(l, item.data, cmd.rt_index != 0x30, cmd.area, cmd.x, cmd.z, cmd.entity_id); } -static void on_set_quest_flag(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (!l->is_game()) { return; } @@ -1335,7 +1278,7 @@ static void on_set_quest_flag(shared_ptr, c->game_data.player()->quest_data1[byte_index] &= (~mask); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); if (c->version() == GameVersion::GC) { bool should_send_boss_drop_req = false; @@ -1374,9 +1317,8 @@ static void on_set_quest_flag(shared_ptr, } } -static void on_enemy_hit(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_enemy_hit(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); @@ -1401,17 +1343,16 @@ static void on_enemy_hit(shared_ptr, enemy.last_hit_by_client_id = c->lobby_client_id; } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } -static void on_charge_attack_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_charge_attack_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version != GameVersion::BB) { throw runtime_error("BB-only command sent in non-BB game"); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); const auto& cmd = check_size_t(data, size); auto& disp = c->game_data.player()->disp; @@ -1422,9 +1363,11 @@ static void on_charge_attack_bb(shared_ptr, } } -static void add_player_exp(shared_ptr s, shared_ptr l, shared_ptr c, uint32_t exp) { +static void add_player_exp(shared_ptr c, uint32_t exp) { + auto s = c->require_server_state(); + c->game_data.player()->disp.stats.experience += exp; - send_give_experience(l, c, exp); + send_give_experience(c, exp); bool leveled_up = false; do { @@ -1439,13 +1382,14 @@ static void add_player_exp(shared_ptr s, shared_ptr l, share } } while (c->game_data.player()->disp.stats.level < 199); if (leveled_up) { - send_level_up(l, c); + send_level_up(c); } } -static void on_steal_exp_bb(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, - const void* data, size_t size) { +static void on_steal_exp_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); + if (l->version != GameVersion::BB) { throw runtime_error("BB-only command sent in non-BB game"); } @@ -1476,18 +1420,19 @@ static void on_steal_exp_bb(shared_ptr s, send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", stolen_exp, cmd.enemy_id.load(), name_for_enum(enemy.type)); } - add_player_exp(s, l, c, stolen_exp); + add_player_exp(c, stolen_exp); } } -static void on_enemy_killed_bb(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_enemy_killed_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); + if (l->version != GameVersion::BB) { throw runtime_error("BB-only command sent in non-BB game"); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); const auto& cmd = check_size_t(data, size); @@ -1545,7 +1490,7 @@ static void on_enemy_killed_bb(shared_ptr s, send_text_message_printf(c, "$C5+%" PRIu32 " E-%hX %s", player_exp, cmd.enemy_id.load(), name_for_enum(e.type)); } - add_player_exp(s, l, c, player_exp); + add_player_exp(c, player_exp); } } @@ -1560,9 +1505,7 @@ static void on_enemy_killed_bb(shared_ptr s, } } -void on_meseta_reward_request_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, - const void* data, size_t size) { +void on_meseta_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); auto p = c->game_data.player(); @@ -1573,37 +1516,39 @@ void on_meseta_reward_request_bb(shared_ptr, p->disp.stats.meseta += cmd.amount; } } else if (cmd.amount > 0) { + auto l = c->require_lobby(); + PlayerInventoryItem item; item.data.data1[0] = 0x04; item.data.data2d = cmd.amount.load(); item.data.id = l->generate_item_id(0xFF); c->game_data.player()->add_item(item); - send_create_inventory_item(l, c, item.data); + send_create_inventory_item(c, item.data); } } -void on_item_reward_request_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, - const void* data, size_t size) { +void on_item_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); + auto l = c->require_lobby(); PlayerInventoryItem item; item.data = cmd.item_data; item.data.id = l->generate_item_id(0xFF); c->game_data.player()->add_item(item); - send_create_inventory_item(l, c, item.data); + send_create_inventory_item(c, item.data); } -static void on_destroy_inventory_item(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); + + auto l = c->require_lobby(); if (!l->is_game()) { return; } if (cmd.header.client_id != c->lobby_client_id) { return; } + if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { auto item = c->game_data.player()->remove_item( cmd.item_id, cmd.amount, c->version() != GameVersion::BB); @@ -1616,17 +1561,18 @@ static void on_destroy_inventory_item(shared_ptr, cmd.item_id.load(), name.c_str()); } c->game_data.player()->print_inventory(stderr); - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_destroy_ground_item(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_destroy_ground_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); + + auto l = c->require_lobby(); if (!l->is_game()) { return; } + if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) { auto item = l->remove_item(cmd.item_id); auto name = item.data.name(false); @@ -1637,13 +1583,12 @@ static void on_destroy_ground_item(shared_ptr, send_text_message_printf(c, "$C5DESTROY/GND %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str()); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_identify_item_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { +static void on_identify_item_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); if (!l->is_game()) { @@ -1667,17 +1612,15 @@ static void on_identify_item_bb(shared_ptr, c->game_data.identify_result.data.data1[4] &= 0x7F; l->item_creator->apply_tekker_deltas( c->game_data.identify_result.data, p->disp.visual.section_id); - send_item_identify_result(l, c); + send_item_identify_result(c); } else { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_accept_identify_item_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size) { - +static void on_accept_identify_item_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); @@ -1692,17 +1635,17 @@ static void on_accept_identify_item_bb(shared_ptr, throw runtime_error("accepted item ID does not match previous identify request"); } c->game_data.player()->add_item(c->game_data.identify_result); - send_create_inventory_item(l, c, c->game_data.identify_result.data); + send_create_inventory_item(c, c->game_data.identify_result.data); c->game_data.identify_result.clear(); } else { - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_sell_item_at_shop_bb(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { - +static void on_sell_item_at_shop_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); @@ -1725,12 +1668,12 @@ static void on_sell_item_at_shop_bb(shared_ptr s, cmd.item_id.load(), price, name.c_str()); } - forward_subcommand(l, c, command, flag, data, size); + forward_subcommand(c, command, flag, data, size); } } -static void on_buy_shop_item_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { +static void on_buy_shop_item_bb(shared_ptr c, uint8_t, uint8_t, const void* data, size_t size) { + auto l = c->require_lobby(); if (l->version == GameVersion::BB) { const auto& cmd = check_size_t(data, size); if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { @@ -1752,7 +1695,7 @@ static void on_buy_shop_item_bb(shared_ptr, item.data.id = cmd.inventory_item_id; p->add_item(item); - send_create_inventory_item(l, c, item.data); + send_create_inventory_item(c, item.data); auto name = item.data.name(false); l->log.info("Inventory item %hu:%08" PRIX32 " created via purchase (%s) for %zu meseta", @@ -1766,18 +1709,16 @@ static void on_buy_shop_item_bb(shared_ptr, } } -static void on_medical_center_bb(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, const void*, size_t) { - if (l->version == GameVersion::BB) { +static void on_medical_center_bb(shared_ptr c, uint8_t, uint8_t, const void*, size_t) { + auto l = c->require_lobby(); + if (l->is_game() && (l->version == GameVersion::BB)) { c->game_data.player()->remove_meseta(10, false); } } //////////////////////////////////////////////////////////////////////////////// -typedef void (*subcommand_handler_t)(shared_ptr s, - shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, - const void* data, size_t size); +typedef void (*subcommand_handler_t)(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size); subcommand_handler_t subcommand_handlers[0x100] = { /* 6x00 */ on_invalid, @@ -2038,14 +1979,13 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 6xFF */ nullptr, }; -void on_subcommand_multi(shared_ptr s, shared_ptr l, - shared_ptr c, uint8_t command, uint8_t flag, const string& data) { +void on_subcommand_multi(shared_ptr c, uint8_t command, uint8_t flag, const string& data) { 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))) { // TODO: We should convert these to non-trial formats and vice versa - forward_subcommand(l, c, command, flag, data.data(), data.size()); + forward_subcommand(c, command, flag, data.data(), data.size()); } else { StringReader r(data); while (!r.eof()) { @@ -2070,9 +2010,9 @@ void on_subcommand_multi(shared_ptr s, shared_ptr l, auto fn = subcommand_handlers[header.subcommand]; if (fn) { - fn(s, l, c, command, flag, data, size); + fn(c, command, flag, data, size); } else { - on_unimplemented(s, l, c, command, flag, data, size); + on_unimplemented(c, command, flag, data, size); } } } diff --git a/src/ReceiveSubcommands.hh b/src/ReceiveSubcommands.hh index fd4619a4..4beee02d 100644 --- a/src/ReceiveSubcommands.hh +++ b/src/ReceiveSubcommands.hh @@ -6,8 +6,9 @@ #include "ServerState.hh" void on_subcommand_multi( - std::shared_ptr s, std::shared_ptr l, - std::shared_ptr c, uint8_t command, uint8_t flag, + std::shared_ptr c, + uint8_t command, + uint8_t flag, const std::string& data); bool subcommand_is_implemented(uint8_t which); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index d497430a..c8a7be48 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -188,8 +188,7 @@ prepare_server_init_contents_bb( return cmd; } -void send_server_init_bb(shared_ptr s, shared_ptr c, - uint8_t flags) { +void send_server_init_bb(shared_ptr c, uint8_t flags) { bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE); parray server_key; parray client_key; @@ -201,7 +200,7 @@ void send_server_init_bb(shared_ptr s, shared_ptr c, static const string primary_expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8); static const string secondary_expected_first_data("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8); shared_ptr detector_crypt(new PSOBBMultiKeyDetectorEncryption( - s->bb_private_keys, + c->require_server_state()->bb_private_keys, bb_crypt_initial_client_commands, cmd.basic_cmd.client_key.data(), sizeof(cmd.basic_cmd.client_key))); @@ -225,8 +224,7 @@ void send_server_init_patch(shared_ptr c) { c->channel.crypt_in.reset(new PSOV2Encryption(client_key)); } -void send_server_init( - shared_ptr s, shared_ptr c, uint8_t flags) { +void send_server_init(shared_ptr c, uint8_t flags) { switch (c->version()) { case GameVersion::DC: case GameVersion::PC: @@ -238,7 +236,7 @@ void send_server_init( send_server_init_patch(c); break; case GameVersion::BB: - send_server_init_bb(s, c, flags); + send_server_init_bb(c, flags); break; default: throw logic_error("unimplemented versioned command"); @@ -291,12 +289,11 @@ void send_quest_open_file_t( send_command_t(c, command_num, 0x00, cmd); } -void send_quest_buffer_overflow( - shared_ptr s, shared_ptr c) { +void send_quest_buffer_overflow(shared_ptr c) { // PSO Episode 3 USA doesn't natively support the B2 command, but we can add // it back to the game with some tricky commands. For details on how this // works, see system/ppc/Episode3USAQuestBufferOverflow.s. - auto fn = s->function_code_index->name_to_function.at("Episode3USAQuestBufferOverflow"); + auto fn = c->require_server_state()->function_code_index->name_to_function.at("Episode3USAQuestBufferOverflow"); if (fn->code.size() > 0x400) { throw runtime_error("Episode 3 buffer overflow code must be a single segment"); } @@ -317,7 +314,9 @@ void send_quest_buffer_overflow( void empty_function_call_response_handler(uint32_t, uint32_t) {} -void prepare_client_for_patches(shared_ptr s, shared_ptr c, std::function on_complete) { +void prepare_client_for_patches(shared_ptr c, std::function on_complete) { + auto s = c->require_server_state(); + auto send_version_detect = [s, wc = weak_ptr(c), on_complete]() -> void { auto c = wc.lock(); if (!c) { @@ -326,7 +325,11 @@ void prepare_client_for_patches(shared_ptr s, shared_ptr c, if (c->version() == GameVersion::GC && c->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([s, c, on_complete](uint32_t specific_version, uint32_t) -> void { + 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); on_complete(); @@ -911,8 +914,9 @@ void send_simple_mail(shared_ptr c, uint32_t from_guild_card_number, // info board template -void send_info_board_t(shared_ptr c, shared_ptr l) { +void send_info_board_t(shared_ptr c) { vector> entries; + auto l = c->require_lobby(); for (const auto& c : l->clients) { if (!c.get()) { continue; @@ -925,22 +929,22 @@ void send_info_board_t(shared_ptr c, shared_ptr l) { send_command_vt(c, 0xD8, entries.size(), entries); } -void send_info_board(shared_ptr c, shared_ptr l) { +void send_info_board(shared_ptr c) { if (c->version() == GameVersion::PC || c->version() == GameVersion::PATCH || c->version() == GameVersion::BB) { - send_info_board_t(c, l); + send_info_board_t(c); } else { - send_info_board_t(c, l); + send_info_board_t(c); } } template void send_card_search_result_t( - shared_ptr s, shared_ptr c, shared_ptr result, shared_ptr result_lobby) { + auto s = c->require_server_state(); const auto& port_name = version_to_lobby_port_name.at(static_cast(c->version())); S_GuildCardSearchResult cmd; @@ -969,28 +973,24 @@ void send_card_search_result_t( } cmd.location_string = location_string; cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY; - cmd.extension.lobby_refs[0].item_id = result->lobby_id; + cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id; cmd.extension.player_name = result->game_data.player()->disp.name; send_command_t(c, 0x41, 0x00, cmd); } void send_card_search_result( - shared_ptr s, shared_ptr c, shared_ptr result, shared_ptr result_lobby) { if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) { - send_card_search_result_t( - s, c, result, result_lobby); + send_card_search_result_t(c, result, result_lobby); } else if (c->version() == GameVersion::PC) { - send_card_search_result_t( - s, c, result, result_lobby); + send_card_search_result_t(c, result, result_lobby); } else if (c->version() == GameVersion::BB) { - send_card_search_result_t( - s, c, result, result_lobby); + send_card_search_result_t(c, result, result_lobby); } else { throw logic_error("unimplemented versioned command"); } @@ -1164,9 +1164,10 @@ void send_menu(shared_ptr c, shared_ptr menu, bool is_info_m template void send_game_menu_t( shared_ptr c, - shared_ptr s, bool is_spectator_team_list, bool show_tournaments_only) { + auto s = c->require_server_state(); + vector> entries; { auto& e = entries.emplace_back(); @@ -1258,15 +1259,14 @@ void send_game_menu_t( void send_game_menu( shared_ptr c, - shared_ptr s, bool is_spectator_team_list, bool show_tournaments_only) { if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) { - send_game_menu_t(c, s, is_spectator_team_list, show_tournaments_only); + send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); } else { - send_game_menu_t(c, s, is_spectator_team_list, show_tournaments_only); + send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); } } @@ -1352,11 +1352,12 @@ void send_quest_menu(shared_ptr c, uint32_t menu_id, } } -void send_lobby_list(shared_ptr c, shared_ptr s) { +void send_lobby_list(shared_ptr c) { // This command appears to be deprecated, as PSO expects it to be exactly how // this server sends it, and does not react if it's different, except by // changing the lobby IDs. + auto s = c->require_server_state(); vector entries; for (shared_ptr l : s->all_lobbies()) { if (!(l->flags & Lobby::Flag::DEFAULT)) { @@ -1962,8 +1963,8 @@ static vector generate_stats_change_subcommands( return subs; } -void send_player_stats_change(shared_ptr l, shared_ptr c, - PlayerStatsChange stat, uint32_t amount) { +void send_player_stats_change(shared_ptr c, PlayerStatsChange stat, uint32_t amount) { + auto l = c->require_lobby(); auto subs = generate_stats_change_subcommands(c->lobby_client_id, stat, amount); send_command_vt(l, (subs.size() > 0x400 / sizeof(G_UpdatePlayerStat_6x9A)) ? 0x6C : 0x60, 0x00, subs); } @@ -2047,16 +2048,15 @@ void send_drop_stacked_item(shared_ptr l, const ItemData& item, } } -void send_pick_up_item(shared_ptr l, shared_ptr c, - uint32_t item_id, uint8_t area) { +void send_pick_up_item(shared_ptr c, uint32_t item_id, uint8_t area) { + auto l = c->require_lobby(); uint16_t client_id = c->lobby_client_id; - G_PickUpItem_6x59 cmd = { - {0x59, 0x03, client_id}, client_id, area, item_id}; + G_PickUpItem_6x59 cmd = {{0x59, 0x03, client_id}, client_id, area, item_id}; send_command_t(l, 0x60, 0x00, cmd); } -void send_create_inventory_item(shared_ptr l, shared_ptr c, - const ItemData& item) { +void send_create_inventory_item(shared_ptr c, const ItemData& item) { + auto l = c->require_lobby(); if (c->version() != GameVersion::BB) { throw logic_error("6xBE can only be sent to BB clients"); } @@ -2065,14 +2065,15 @@ void send_create_inventory_item(shared_ptr l, shared_ptr c, send_command_t(l, 0x60, 0x00, cmd); } -void send_destroy_item(shared_ptr l, shared_ptr c, - uint32_t item_id, uint32_t amount) { +void send_destroy_item(shared_ptr c, uint32_t item_id, uint32_t amount) { + auto l = c->require_lobby(); uint16_t client_id = c->lobby_client_id; G_DeleteInventoryItem_6x29 cmd = {{0x29, 0x03, client_id}, item_id, amount}; send_command_t(l, 0x60, 0x00, cmd); } -void send_item_identify_result(shared_ptr l, shared_ptr c) { +void send_item_identify_result(shared_ptr c) { + auto l = c->require_lobby(); if (c->version() != GameVersion::BB) { throw logic_error("cannot send item identify result to non-BB client"); } @@ -2124,7 +2125,8 @@ void send_shop(shared_ptr c, uint8_t shop_type) { } // notifies players about a level up -void send_level_up(shared_ptr l, shared_ptr c) { +void send_level_up(shared_ptr c) { + auto l = c->require_lobby(); CharacterStats stats = c->game_data.player()->disp.stats.char_stats; for (size_t x = 0; x < c->game_data.player()->inventory.num_items; x++) { @@ -2150,8 +2152,8 @@ void send_level_up(shared_ptr l, shared_ptr c) { send_command_t(l, 0x60, 0x00, cmd); } -void send_give_experience(shared_ptr l, shared_ptr c, - uint32_t amount) { +void send_give_experience(shared_ptr c, uint32_t amount) { + auto l = c->require_lobby(); if (c->version() != GameVersion::BB) { throw logic_error("6xBF can only be sent to BB clients"); } @@ -2187,8 +2189,9 @@ void send_rare_enemy_index_list(shared_ptr c, const vector& inde //////////////////////////////////////////////////////////////////////////////// // ep3 only commands -void send_ep3_card_list_update(shared_ptr s, shared_ptr c) { +void send_ep3_card_list_update(shared_ptr c) { if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) { + auto s = c->require_server_state(); const auto& data = (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) ? s->ep3_card_index_trial->get_compressed_definitions() : s->ep3_card_index->get_compressed_definitions(); @@ -2215,7 +2218,8 @@ void send_ep3_media_update( send_command(c, 0xB9, 0x00, w.str()); } -void send_ep3_rank_update(shared_ptr s, shared_ptr c) { +void send_ep3_rank_update(shared_ptr c) { + auto s = c->require_server_state(); uint32_t meseta = s->ep3_infinite_meseta ? 1000000 : 0; S_RankUpdate_GC_Ep3_B7 cmd = {0, "\0\0\0\0\0\0\0\0\0\0\0", meseta, meseta, 0xFFFFFFFF}; send_command_t(c, 0xB7, 0x00, cmd); @@ -2259,17 +2263,15 @@ void send_ep3_set_context_token(shared_ptr c, uint32_t context_token) { } void send_ep3_confirm_tournament_entry( - shared_ptr s, shared_ptr c, shared_ptr tourn) { - // WARNING: s is permitted to be null if tourn is null - if (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) { throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client"); } S_ConfirmTournamentEntry_GC_Ep3_CC cmd; if (tourn) { + auto s = c->require_server_state(); cmd.tournament_name = tourn->get_name(); cmd.server_name = encode_sjis(s->name); // TODO: Fill this in appropriately when we support scheduled start times @@ -2286,9 +2288,10 @@ void send_ep3_confirm_tournament_entry( } void send_ep3_tournament_list( - shared_ptr s, shared_ptr c, bool is_for_spectator_team_create) { + auto s = c->require_server_state(); + S_TournamentList_GC_Ep3_E0 cmd; size_t z = 0; for (const auto& it : s->ep3_tournament_index->all_tournaments()) { @@ -2496,11 +2499,11 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { } } -void send_ep3_set_tournament_player_decks( - shared_ptr s, - shared_ptr l, - shared_ptr c, - shared_ptr match) { +void send_ep3_set_tournament_player_decks(shared_ptr c) { + auto s = c->require_server_state(); + auto l = c->require_lobby(); + + auto& match = l->tournament_match; auto tourn = match->tournament.lock(); if (!tourn) { throw runtime_error("tournament is deleted"); @@ -2554,8 +2557,9 @@ void send_ep3_set_tournament_player_decks( // TODO: Handle disconnection during the match (the other team should win) } -void send_ep3_tournament_match_result( - shared_ptr s, shared_ptr l, shared_ptr match) { +void send_ep3_tournament_match_result(shared_ptr l) { + auto s = l->require_server_state(); + auto& match = l->tournament_match; auto tourn = match->tournament.lock(); if (!tourn) { return; @@ -2776,7 +2780,8 @@ bool send_quest_barrier_if_all_clients_ready(shared_ptr l) { return true; } -void send_ep3_card_auction(shared_ptr s, shared_ptr l) { +void send_ep3_card_auction(shared_ptr l) { + auto s = l->require_server_state(); if ((s->ep3_card_auction_points == 0) || (s->ep3_card_auction_min_size == 0) || (s->ep3_card_auction_max_size == 0)) { diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 01bb29a1..05ba5643 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -127,18 +127,13 @@ prepare_server_init_contents_bb( const parray& server_key, const parray& client_key, uint8_t flags); -void send_server_init( - std::shared_ptr s, - std::shared_ptr c, - uint8_t flags); +void send_server_init(std::shared_ptr c, uint8_t flags); void send_update_client_config(std::shared_ptr c); void empty_function_call_response_handler(uint32_t, uint32_t); -void send_quest_buffer_overflow( - std::shared_ptr s, std::shared_ptr c); -void prepare_client_for_patches( - std::shared_ptr s, std::shared_ptr c, std::function on_complete); +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, @@ -190,7 +185,7 @@ void send_ship_info(Channel& ch, const std::u16string& text); void send_text_message(Channel& ch, const std::u16string& text); void send_text_message(std::shared_ptr c, const std::u16string& text); void send_text_message(std::shared_ptr l, const std::u16string& text); -void send_text_message(std::shared_ptr l, const std::u16string& text); +void send_text_message(std::shared_ptr s, const std::u16string& text); std::u16string prepare_chat_message( GameVersion version, @@ -235,10 +230,9 @@ __attribute__((format(printf, 2, 3))) void send_text_message_printf( __attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf( std::shared_ptr s, const char* format, ...); -void send_info_board(std::shared_ptr c, std::shared_ptr l); +void send_info_board(std::shared_ptr c); void send_card_search_result( - std::shared_ptr s, std::shared_ptr c, std::shared_ptr result, std::shared_ptr result_lobby); @@ -255,32 +249,26 @@ void send_guild_card(std::shared_ptr c, std::shared_ptr source); void send_menu(std::shared_ptr c, std::shared_ptr menu, bool is_info_menu = false); void send_game_menu( std::shared_ptr c, - std::shared_ptr s, bool is_spectator_team_list, bool is_tournament_game_list); void send_quest_menu(std::shared_ptr c, uint32_t menu_id, const std::vector>& quests, bool is_download_menu); void send_quest_menu(std::shared_ptr c, uint32_t menu_id, std::shared_ptr category_index, uint8_t flags); -void send_lobby_list(std::shared_ptr c, std::shared_ptr s); +void send_lobby_list(std::shared_ptr c); void send_player_records(std::shared_ptr c, std::shared_ptr l, std::shared_ptr joining_client = nullptr); void send_join_lobby(std::shared_ptr c, std::shared_ptr l); -void send_player_join_notification(std::shared_ptr c, - std::shared_ptr l, std::shared_ptr joining_client); -void send_player_leave_notification(std::shared_ptr l, - uint8_t leaving_client_id); +void send_player_join_notification(std::shared_ptr c, std::shared_ptr l, std::shared_ptr joining_client); +void send_player_leave_notification(std::shared_ptr l, uint8_t leaving_client_id); void send_self_leave_notification(std::shared_ptr c); void send_get_player_info(std::shared_ptr c); -void send_execute_item_trade(std::shared_ptr c, - const std::vector& items); -void send_execute_card_trade(std::shared_ptr c, - const std::vector>& card_to_count); +void send_execute_item_trade(std::shared_ptr c, const std::vector& items); +void send_execute_card_trade(std::shared_ptr c, const std::vector>& card_to_count); void send_arrow_update(std::shared_ptr l); -void send_resume_game(std::shared_ptr l, - std::shared_ptr ready_client); +void send_resume_game(std::shared_ptr l, std::shared_ptr ready_client); enum PlayerStatsChange { SUBTRACT_HP = 0, @@ -290,8 +278,7 @@ enum PlayerStatsChange { ADD_TP = 4, }; -void send_player_stats_change(std::shared_ptr l, std::shared_ptr c, - PlayerStatsChange stat, uint32_t amount); +void send_player_stats_change(std::shared_ptr c, PlayerStatsChange stat, uint32_t amount); void send_player_stats_change( Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount); void send_warp(Channel& ch, uint8_t client_id, uint32_t area, bool is_private); @@ -299,9 +286,8 @@ void send_warp(std::shared_ptr c, uint32_t area, bool is_private); void send_warp(std::shared_ptr l, uint32_t area, bool is_private); void send_ep3_change_music(Channel& ch, uint32_t song); -void send_set_player_visibility(std::shared_ptr l, - std::shared_ptr c, bool visible); -void send_revive_player(std::shared_ptr l, std::shared_ptr c); +void send_set_player_visibility(std::shared_ptr c, bool visible); +void send_revive_player(std::shared_ptr c); void send_drop_item(Channel& ch, const ItemData& item, bool from_enemy, uint8_t area, float x, float z, uint16_t request_id); @@ -311,37 +297,30 @@ void send_drop_stacked_item(Channel& ch, const ItemData& item, uint8_t area, float x, float z); void send_drop_stacked_item(std::shared_ptr l, const ItemData& item, uint8_t area, float x, float z); -void send_pick_up_item(std::shared_ptr l, std::shared_ptr c, uint32_t id, - uint8_t area); -void send_create_inventory_item(std::shared_ptr l, std::shared_ptr c, - const ItemData& item); -void send_destroy_item(std::shared_ptr l, std::shared_ptr c, - uint32_t item_id, uint32_t amount); -void send_item_identify_result(std::shared_ptr l, std::shared_ptr c); +void send_pick_up_item(std::shared_ptr c, uint32_t id, uint8_t area); +void send_create_inventory_item(std::shared_ptr c, const ItemData& item); +void send_destroy_item(std::shared_ptr c, uint32_t item_id, uint32_t amount); +void send_item_identify_result(std::shared_ptr c); void send_bank(std::shared_ptr c); void send_shop(std::shared_ptr c, uint8_t shop_type); -void send_level_up(std::shared_ptr l, std::shared_ptr c); -void send_give_experience(std::shared_ptr l, std::shared_ptr c, - uint32_t amount); +void send_level_up(std::shared_ptr c); +void send_give_experience(std::shared_ptr c, uint32_t amount); void send_set_exp_multiplier(std::shared_ptr l); void send_rare_enemy_index_list(std::shared_ptr c, const std::vector& indexes); -void send_ep3_card_list_update( - std::shared_ptr s, std::shared_ptr c); +void send_ep3_card_list_update(std::shared_ptr c); void send_ep3_media_update( std::shared_ptr c, uint32_t type, uint32_t which, const std::string& compressed_data); -void send_ep3_rank_update(std::shared_ptr s, std::shared_ptr c); +void send_ep3_rank_update(std::shared_ptr c); void send_ep3_card_battle_table_state(std::shared_ptr l, uint16_t table_number); void send_ep3_set_context_token(std::shared_ptr c, uint32_t context_token); void send_ep3_confirm_tournament_entry( - std::shared_ptr s, std::shared_ptr c, std::shared_ptr t); void send_ep3_tournament_list( - std::shared_ptr s, std::shared_ptr c, bool is_for_spectator_team_create); void send_ep3_tournament_entry_list( @@ -351,15 +330,8 @@ void send_ep3_tournament_entry_list( void send_ep3_tournament_info( std::shared_ptr c, std::shared_ptr t); -void send_ep3_set_tournament_player_decks( - std::shared_ptr s, - std::shared_ptr l, - std::shared_ptr c, - std::shared_ptr match); -void send_ep3_tournament_match_result( - std::shared_ptr s, - std::shared_ptr l, - std::shared_ptr match); +void send_ep3_set_tournament_player_decks(std::shared_ptr c); +void send_ep3_tournament_match_result(std::shared_ptr l); void send_ep3_tournament_details( std::shared_ptr c, @@ -367,7 +339,7 @@ void send_ep3_tournament_details( void send_ep3_game_details( std::shared_ptr c, std::shared_ptr l); void send_ep3_update_spectator_count(std::shared_ptr l); -void send_ep3_card_auction(std::shared_ptr s, std::shared_ptr l); +void send_ep3_card_auction(std::shared_ptr l); void send_ep3_disband_watcher_lobbies(std::shared_ptr primary_l); // Pass mask_key = 0 to unmask the command diff --git a/src/Server.cc b/src/Server.cc index 60efa81e..8995c056 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -42,7 +42,7 @@ void Server::disconnect_client(shared_ptr c) { c->channel.disconnect(); try { - on_disconnect(this->state, c); + on_disconnect(c); } catch (const exception& e) { server_log.warning("Error during client disconnect cleanup: %s", e.what()); } @@ -91,13 +91,13 @@ void Server::dispatch_on_listen_accept( socklen); } -void Server::dispatch_on_listen_error(struct evconnlistener* listener, - void* ctx) { +void Server::dispatch_on_listen_error( + struct evconnlistener* listener, void* ctx) { reinterpret_cast(ctx)->on_listen_error(listener); } -void Server::on_listen_accept(struct evconnlistener* listener, - evutil_socket_t fd, struct sockaddr*, int) { +void Server::on_listen_accept( + struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) { int listen_fd = evconnlistener_get_fd(listener); ListeningSocket* listening_socket; @@ -113,7 +113,7 @@ void Server::on_listen_accept(struct evconnlistener* listener, struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); shared_ptr c(new Client( - bev, listening_socket->version, listening_socket->behavior)); + this->shared_from_this(), bev, listening_socket->version, listening_socket->behavior)); c->game_data.should_save = this->state->allow_saving; c->channel.on_command_received = Server::on_client_input; c->channel.on_error = Server::on_client_error; @@ -124,7 +124,7 @@ void Server::on_listen_accept(struct evconnlistener* listener, c->id, fd, listen_fd, listening_socket->addr_str.c_str()); try { - on_connect(this->state, c); + on_connect(c); } catch (const exception& e) { server_log.warning("Error during client initialization: %s", e.what()); this->disconnect_client(c); @@ -134,7 +134,7 @@ void Server::on_listen_accept(struct evconnlistener* listener, void Server::connect_client( struct bufferevent* bev, uint32_t address, uint16_t client_port, uint16_t server_port, GameVersion version, ServerBehavior initial_state) { - shared_ptr c(new Client(bev, version, initial_state)); + shared_ptr c(new Client(this->shared_from_this(), bev, version, initial_state)); c->game_data.should_save = this->state->allow_saving; c->channel.on_command_received = Server::on_client_input; c->channel.on_error = Server::on_client_error; @@ -158,7 +158,7 @@ void Server::connect_client( remote_sin->sin_port = htons(client_port); try { - on_connect(this->state, c); + on_connect(c); } catch (const exception& e) { server_log.error("Error during client initialization: %s", e.what()); this->disconnect_client(c); @@ -181,13 +181,13 @@ void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std:: } else { if (server->state->catch_handler_exceptions) { try { - on_command(server->state, c, command, flag, data); + on_command(c, command, flag, data); } catch (const exception& e) { server_log.warning("Error processing client command: %s", e.what()); c->should_disconnect = true; } } else { - on_command(server->state, c, command, flag, data); + on_command(c, command, flag, data); } if (c->should_disconnect) { server->disconnect_client(c); diff --git a/src/Server.hh b/src/Server.hh index e80e413a..98bf45c4 100644 --- a/src/Server.hh +++ b/src/Server.hh @@ -10,7 +10,7 @@ #include "Client.hh" #include "ServerState.hh" -class Server { +class Server : public std::enable_shared_from_this { public: Server() = delete; Server(const Server&) = delete; @@ -27,12 +27,17 @@ public: void connect_client(struct bufferevent* bev, uint32_t address, uint16_t client_port, uint16_t server_port, GameVersion version, ServerBehavior initial_state); + void disconnect_client(std::shared_ptr c); std::shared_ptr get_client() const; std::vector> get_clients_by_identifier( const std::string& ident) const; std::shared_ptr get_base() const; + inline std::shared_ptr get_state() const { + return this->state; + } + private: std::shared_ptr base; std::shared_ptr destroy_clients_ev; @@ -64,8 +69,6 @@ private: evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx); static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx); - void disconnect_client(std::shared_ptr c); - void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen); void on_listen_error(struct evconnlistener* listener); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index d43bef83..0d1a4bc9 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -569,7 +569,7 @@ Proxy session commands:\n\ if (tourn) { tourn->start(); this->state->ep3_tournament_index->save(); - tourn->send_all_state_updates(this->state); + tourn->send_all_state_updates(); send_ep3_text_message_printf(this->state, "$C7The tournament\n$C6%s$C7\nhas begun", tourn->get_name().c_str()); fprintf(stderr, "tournament started\n"); } else { @@ -625,7 +625,7 @@ Proxy session commands:\n\ if (c) { if (command_name[1] == 's') { - on_command_with_header(this->state, c, data); + on_command_with_header(c, data); } else { send_command_with_header(c->channel, data.data(), data.size()); } diff --git a/src/ServerState.cc b/src/ServerState.cc index a38f242c..efa9b6b9 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -113,7 +113,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { if (c->preferred_lobby_id >= 0) { try { auto l = this->find_lobby(c->preferred_lobby_id); - if (!l->is_game() && (l->flags & Lobby::Flag::PUBLIC)) { + if (l && !l->is_game() && (l->flags & Lobby::Flag::PUBLIC)) { l->add_client(c); added_to_lobby = l; } @@ -148,12 +148,14 @@ void ServerState::add_client_to_available_lobby(shared_ptr c) { } void ServerState::remove_client_from_lobby(shared_ptr c) { - auto l = this->id_to_lobby.at(c->lobby_id); - l->remove_client(c); - if (!(l->flags & Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) { - this->remove_lobby(l->lobby_id); - } else { - send_player_leave_notification(l, c->lobby_client_id); + auto l = c->lobby.lock(); + if (l) { + l->remove_client(c); + if (!(l->flags & Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) { + this->remove_lobby(l->lobby_id); + } else { + send_player_leave_notification(l, c->lobby_client_id); + } } } @@ -164,7 +166,7 @@ bool ServerState::change_client_lobby( ssize_t required_client_id) { uint8_t old_lobby_client_id = c->lobby_client_id; - shared_ptr current_lobby = this->find_lobby(c->lobby_id); + auto current_lobby = c->lobby.lock(); try { if (current_lobby) { current_lobby->move_client_to_lobby(new_lobby, c, required_client_id); @@ -210,7 +212,11 @@ void ServerState::send_lobby_join_notifications(shared_ptr l, } shared_ptr ServerState::find_lobby(uint32_t lobby_id) { - return this->id_to_lobby.at(lobby_id); + try { + return this->id_to_lobby.at(lobby_id); + } catch (const out_of_range&) { + return nullptr; + } } vector> ServerState::all_lobbies() { @@ -225,7 +231,7 @@ shared_ptr ServerState::create_lobby() { while (this->id_to_lobby.count(this->next_lobby_id)) { this->next_lobby_id++; } - shared_ptr l(new Lobby(this->next_lobby_id++)); + shared_ptr l(new Lobby(this->shared_from_this(), this->next_lobby_id++)); this->id_to_lobby.emplace(l->lobby_id, l); l->log.info("Created lobby"); return l; diff --git a/tests/config.json b/tests/config.json index 74cf0e52..9776aa47 100644 --- a/tests/config.json +++ b/tests/config.json @@ -8,6 +8,7 @@ // 3. Unregistered users are allowed. This enables the tests to run on other // machines, which won't have the same license file. "ServerName": "Alexandria", + "CatchHandlerExceptions": false, "LocalAddress": "en0", "ExternalAddress": "en0",