#include "ChatCommands.hh" #include #include #include #include #include #include #include "Loggers.hh" #include "Server.hh" #include "ProxyServer.hh" #include "Lobby.hh" #include "Client.hh" #include "SendCommands.hh" #include "Text.hh" #include "StaticGameData.hh" using namespace std; //////////////////////////////////////////////////////////////////////////////// // Checks class precondition_failed { public: precondition_failed(const std::u16string& user_msg) : user_msg(user_msg) { } ~precondition_failed() = default; const std::u16string& what() const { return this->user_msg; } private: std::u16string user_msg; }; static void check_privileges(shared_ptr c, uint64_t mask) { if (!c->license) { throw precondition_failed(u"$C6You are not\nlogged in."); } if ((c->license->privileges & mask) != mask) { throw precondition_failed(u"$C6You do not have\npermission to\nrun this command."); } } static void check_version(shared_ptr c, GameVersion version) { if (c->version() != version) { throw precondition_failed(u"$C6This command cannot\nbe used for your\nversion of PSO."); } } static void check_not_version(shared_ptr c, GameVersion version) { if (c->version() == version) { throw precondition_failed(u"$C6This command cannot\nbe used for your\nversion of PSO."); } } static void check_is_game(shared_ptr l, bool is_game) { if (l->is_game() != is_game) { throw precondition_failed(is_game ? u"$C6This command cannot\nbe used in lobbies." : u"$C6This command cannot\nbe used in games."); } } static void check_is_ep3(shared_ptr c, bool is_ep3) { if (!!(c->flags & Client::Flag::IS_EPISODE_3) != is_ep3) { throw precondition_failed(is_ep3 ? u"$C6This command can only\nbe used in Episode 3." : u"$C6This command cannot\nbe used in Episode 3."); } } static void check_cheats_enabled(shared_ptr l) { if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { throw precondition_failed(u"$C6This command can\nonly be used in\ncheat mode."); } } static void check_is_leader(shared_ptr l, shared_ptr c) { if (l->leader_id != c->lobby_client_id) { throw precondition_failed(u"$C6This command can\nonly be used by\nthe game leader."); } } //////////////////////////////////////////////////////////////////////////////// // Message commands static void server_command_lobby_info(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { // no preconditions - everyone can use this command if (!l) { send_text_message(c, u"$C6No lobby information"); } else if (l->is_game()) { string level_string; if (l->max_level == 0xFFFFFFFF) { level_string = string_printf("Levels: %d+", l->min_level + 1); } else { level_string = string_printf("Levels: %d-%d", l->min_level + 1, l->max_level + 1); } send_text_message_printf(c, "$C6Game ID: %08X\n%s\nSection ID: %s\nCheat mode: %s", l->lobby_id, level_string.c_str(), name_for_section_id(l->section_id).c_str(), (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "on" : "off"); } else { size_t num_clients = l->count_clients(); size_t max_clients = l->max_clients; send_text_message_printf(c, "$C6Lobby ID: %08X\nPlayers: %zu/%zu", l->lobby_id, num_clients, max_clients); } } static void proxy_command_lobby_info(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string&) { string msg; if (session.license) { msg = string_printf("$C7GC: $C6%" PRId64 "\n", session.remote_guild_card_number); } msg += string_printf("$C7Client ID: $C6%zu%s", session.lobby_client_id, (session.leader_client_id == session.lobby_client_id) ? " (L)" : ""); vector cheats_tokens; if (session.switch_assist) { cheats_tokens.emplace_back("SWA"); } if (session.infinite_hp) { cheats_tokens.emplace_back("HP"); } if (session.infinite_tp) { cheats_tokens.emplace_back("TP"); } if (!cheats_tokens.empty()) { msg += "\n$C7Cheats: $C6"; msg += join(cheats_tokens, ","); } vector behaviors_tokens; if (session.save_files) { behaviors_tokens.emplace_back("SAVE"); } if (session.suppress_remote_login) { behaviors_tokens.emplace_back("SL"); } if (session.function_call_return_value >= 0) { behaviors_tokens.emplace_back("BFC"); } if (!behaviors_tokens.empty()) { msg += "\n$C7Flags: $C6"; msg += join(behaviors_tokens, ","); } if (session.override_section_id >= 0) { msg += "\n$C7SecID override: $C6"; msg += name_for_section_id(session.override_section_id); } send_text_message(session.client_channel, decode_sjis(msg)); } static void server_command_ax(shared_ptr, shared_ptr, 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) { 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 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 server_command_dbgid(shared_ptr, shared_ptr, shared_ptr c, const std::u16string&) { c->prefer_high_lobby_client_id = !c->prefer_high_lobby_client_id; send_text_message_printf(c, "ID preference set\nto $C6%s", c->prefer_high_lobby_client_id ? "high" : "low"); } static void server_command_proxygc(shared_ptr, shared_ptr, shared_ptr c, const std::u16string& args) { if (args.empty()) { client_options_cache.delete_key( string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number)); send_text_message(c, u"Proxy remote Guild\nCard number cleared"); } else { uint32_t proxy_remote_guild_card_number = stoll(encode_sjis(args), nullptr, 0); client_options_cache.replace( string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number), string_printf("%08" PRIu32, proxy_remote_guild_card_number)); send_text_message_printf(c, "Proxy remote Guild\nCard number set to\n$C6%" PRIu32, proxy_remote_guild_card_number); } } static void proxy_command_proxygc(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string& args) { session.remote_guild_card_number = stoll(encode_sjis(args), nullptr, 0); } static void server_command_persist(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_privileges(c, Privilege::DEBUG); if (l->flags & Lobby::Flag::DEFAULT) { send_text_message(c, u"$C6Default lobbies\ncannot be marked\ntemporary"); } else { l->flags ^= Lobby::Flag::PERSISTENT; send_text_message_printf(c, "Lobby persistence\n%s", (l->flags & Lobby::Flag::PERSISTENT) ? "enabled" : "disabled"); } } static void server_command_get_self_card(shared_ptr, shared_ptr, 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) { string args = encode_sjis(u16args); bool any_card_sent = false; for (const auto& p : session.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); any_card_sent = true; } } if (!any_card_sent) { try { size_t index = stoull(args, nullptr, 0); const auto& p = session.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); } } catch (const exception& e) { send_text_message_printf(session.client_channel, "Error: %s", e.what()); } } } //////////////////////////////////////////////////////////////////////////////// // Lobby commands static void server_command_cheat(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); check_is_leader(l, c); l->flags ^= Lobby::Flag::CHEATS_ENABLED; send_text_message_printf(l, "Cheat mode %s", (l->flags & Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); // if cheat mode was disabled, turn off all the cheat features that were on if (!(l->flags & Lobby::Flag::CHEATS_ENABLED)) { for (size_t x = 0; x < l->max_clients; x++) { auto c = l->clients[x]; if (!c) { continue; } c->infinite_hp = false; c->infinite_tp = false; c->switch_assist = false; } l->next_drop_item = PlayerInventoryItem(); } } static void server_command_lobby_event(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, false); check_privileges(c, Privilege::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { send_text_message(c, u"$C6No such lobby event."); return; } l->event = new_event; send_change_event(l, l->event); } static void proxy_command_lobby_event(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string& args) { if (args.empty()) { session.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."); } else { session.override_lobby_event = new_event; if ((session.version == GameVersion::GC && !(session.newserv_client_config.cfg.flags & Client::Flag::IS_TRIAL_EDITION)) || (session.version == GameVersion::XB) || (session.version == GameVersion::BB)) { session.client_channel.send(0xDA, session.override_lobby_event); } } } } static void server_command_lobby_event_all(shared_ptr s, shared_ptr, shared_ptr c, const std::u16string& args) { check_privileges(c, Privilege::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { send_text_message(c, u"$C6No such lobby event."); return; } for (auto l : s->all_lobbies()) { if (l->is_game() || !(l->flags & Lobby::Flag::DEFAULT)) { continue; } l->event = new_event; send_change_event(l, l->event); } } static void server_command_lobby_type(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, false); check_privileges(c, Privilege::CHANGE_EVENT); uint8_t new_type = lobby_type_for_name(args); if (new_type == 0x80) { send_text_message(c, u"$C6No such lobby type."); return; } l->type = new_type; if (l->type < ((l->flags & Lobby::Flag::EPISODE_3_ONLY) ? 20 : 15)) { l->type = l->block - 1; } for (size_t x = 0; x < l->max_clients; x++) { if (l->clients[x]) { send_join_lobby(l->clients[x], l); } } } //////////////////////////////////////////////////////////////////////////////// // Game commands static void server_command_secid(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, false); if (!args[0]) { c->override_section_id = -1; send_text_message(c, u"$C6Override section ID\nremoved"); } else { uint8_t new_secid = section_id_for_name(args); if (new_secid == 0xFF) { send_text_message(c, u"$C6Invalid section ID"); } else { c->override_section_id = new_secid; send_text_message(c, u"$C6Override section ID\nset"); } } } static void proxy_command_secid(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string& args) { if (!args[0]) { session.override_section_id = -1; send_text_message(session.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"); } else { session.override_section_id = new_secid; send_text_message(session.client_channel, u"$C6Override section ID\nset"); } } } static void server_command_rand(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, false); if (!args[0]) { c->override_random_seed = -1; send_text_message(c, u"$C6Override seed\nremoved"); } else { c->override_random_seed = stoul(encode_sjis(args), 0, 16); send_text_message(c, u"$C6Override seed\nset"); } } static void proxy_command_rand(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string& args) { if (!args[0]) { session.override_random_seed = -1; send_text_message(session.client_channel, u"$C6Override seed\nremoved"); } else { session.override_random_seed = stoul(encode_sjis(args), 0, 16); send_text_message(session.client_channel, u"$C6Override seed\nset"); } } static void server_command_password(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, true); check_is_leader(l, c); if (!args[0]) { l->password[0] = 0; send_text_message(l, u"$C6Game unlocked"); } else { l->password = args; auto encoded = encode_sjis(l->password); send_text_message_printf(l, "$C6Game password:\n%s", encoded.c_str()); } } static void server_command_min_level(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, true); check_is_leader(l, c); u16string buffer; l->min_level = stoull(encode_sjis(args)) - 1; send_text_message_printf(l, "$C6Minimum level set to %" PRIu32, l->min_level + 1); } static void server_command_max_level(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, true); check_is_leader(l, c); l->max_level = stoull(encode_sjis(args)) - 1; if (l->max_level >= 200) { l->max_level = 0xFFFFFFFF; } if (l->max_level == 0xFFFFFFFF) { send_text_message(l, u"$C6Maximum level set to unlimited"); } else { send_text_message_printf(l, "$C6Maximum level set to %" PRIu32, l->max_level + 1); } } //////////////////////////////////////////////////////////////////////////////// // Character commands static void server_command_edit(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, false); check_version(c, GameVersion::BB); string encoded_args = tolower(encode_sjis(args)); vector tokens = split(encoded_args, ' '); try { if (tokens.at(0) == "atp") { c->game_data.player()->disp.stats.atp = stoul(tokens.at(1)); } else if (tokens.at(0) == "mst") { c->game_data.player()->disp.stats.mst = stoul(tokens.at(1)); } else if (tokens.at(0) == "evp") { c->game_data.player()->disp.stats.evp = stoul(tokens.at(1)); } else if (tokens.at(0) == "hp") { c->game_data.player()->disp.stats.hp = stoul(tokens.at(1)); } else if (tokens.at(0) == "dfp") { c->game_data.player()->disp.stats.dfp = stoul(tokens.at(1)); } else if (tokens.at(0) == "ata") { c->game_data.player()->disp.stats.ata = stoul(tokens.at(1)); } else if (tokens.at(0) == "lck") { c->game_data.player()->disp.stats.lck = stoul(tokens.at(1)); } else if (tokens.at(0) == "meseta") { c->game_data.player()->disp.meseta = stoul(tokens.at(1)); } else if (tokens.at(0) == "exp") { c->game_data.player()->disp.experience = stoul(tokens.at(1)); } else if (tokens.at(0) == "level") { c->game_data.player()->disp.level = stoul(tokens.at(1)) - 1; } else if (tokens.at(0) == "namecolor") { uint32_t new_color; sscanf(tokens.at(1).c_str(), "%8X", &new_color); c->game_data.player()->disp.name_color = new_color; } else if (tokens.at(0) == "secid") { uint8_t secid = section_id_for_name(decode_sjis(tokens.at(1))); if (secid == 0xFF) { send_text_message(c, u"$C6No such section ID"); return; } else { c->game_data.player()->disp.section_id = secid; } } else if (tokens.at(0) == "name") { c->game_data.player()->disp.name = add_language_marker(tokens.at(1), 'J'); } else if (tokens.at(0) == "npc") { if (tokens.at(1) == "none") { c->game_data.player()->disp.extra_model = 0; c->game_data.player()->disp.v2_flags &= 0xFD; } else { uint8_t npc = npc_for_name(decode_sjis(tokens.at(1))); if (npc == 0xFF) { send_text_message(c, u"$C6No such NPC"); return; } c->game_data.player()->disp.extra_model = npc; c->game_data.player()->disp.v2_flags |= 0x02; } } else if (tokens.at(0) == "tech") { uint8_t level = stoul(tokens.at(2)) - 1; if (tokens.at(1) == "all") { for (size_t x = 0; x < 0x14; x++) { c->game_data.player()->disp.technique_levels.data()[x] = level; } } else { uint8_t tech_id = technique_for_name(decode_sjis(tokens.at(1))); if (tech_id == 0xFF) { send_text_message(c, u"$C6No such technique"); return; } try { c->game_data.player()->disp.technique_levels[tech_id] = level; } catch (const out_of_range&) { send_text_message(c, u"$C6Invalid technique"); return; } } } else { send_text_message(c, u"$C6Unknown field"); return; } } catch (const out_of_range&) { send_text_message(c, u"$C6Not enough arguments"); return; } // Reload the client in the lobby send_player_leave_notification(l, c->lobby_client_id); send_complete_player_bb(c); s->send_lobby_join_notifications(l, c); } // TODO: implement this // TODO: make sure the bank name is filesystem-safe /* static void server_command_change_bank(shared_ptr, shared_ptr, shared_ptr c, const std::u16string&) { check_version(c, GameVersion::BB); TODO } */ // 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) { check_is_game(l, false); check_not_version(c, GameVersion::BB); vector tokens = split(encode_sjis(args), L' '); if (tokens.size() != 3) { send_text_message(c, u"$C6Incorrect argument count"); return; } // username/password are tokens[0] and [1] c->pending_bb_save_player_index = stoul(tokens[2]) - 1; if (c->pending_bb_save_player_index > 3) { send_text_message(c, u"$C6Player index must be 1-4"); return; } try { s->license_manager->verify_bb(tokens[0].c_str(), tokens[1].c_str()); } catch (const exception& e) { send_text_message_printf(c, "$C6Login failed: %s", e.what()); return; } c->pending_bb_save_username = tokens[0]; // Request the player data. The client will respond with a 61, and the handler // for that command will execute the conversion send_command(c, 0x95, 0x00); } //////////////////////////////////////////////////////////////////////////////// // Administration commands static string name_for_client(shared_ptr c) { auto player = c->game_data.player(false); if (player.get()) { return encode_sjis(player->disp.name); } if (c->license.get()) { return string_printf("SN:%" PRIu32, c->license->serial_number); } return "Player"; } static void server_command_silence(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { check_privileges(c, Privilege::SILENCE_USER); auto target = s->find_client(&args); if (!target->license) { // this should be impossible, but I'll bet it's not actually send_text_message(c, u"$C6Client not logged in"); return; } if (target->license->privileges & Privilege::MODERATOR) { send_text_message(c, u"$C6You do not have\nsufficient privileges."); return; } target->can_chat = !target->can_chat; string target_name = name_for_client(target); send_text_message_printf(l, "$C6%s %ssilenced", target_name.c_str(), target->can_chat ? "un" : ""); } static void server_command_kick(shared_ptr s, shared_ptr l, shared_ptr c, const std::u16string& args) { check_privileges(c, Privilege::KICK_USER); auto target = s->find_client(&args); if (!target->license) { // This should be impossible, but I'll bet it's not actually send_text_message(c, u"$C6Client not logged in"); return; } if (target->license->privileges & Privilege::MODERATOR) { send_text_message(c, u"$C6You do not have\nsufficient privileges."); return; } send_message_box(target, u"$C6You were kicked off by a moderator."); target->should_disconnect = true; string target_name = name_for_client(target); 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) { check_privileges(c, Privilege::BAN_USER); u16string args_str(args); size_t space_pos = args_str.find(L' '); if (space_pos == string::npos) { send_text_message(c, u"$C6Incorrect argument count"); return; } u16string identifier = args_str.substr(space_pos + 1); auto target = s->find_client(&identifier); if (!target->license) { // This should be impossible, but I'll bet it's not actually send_text_message(c, u"$C6Client not logged in"); return; } if (target->license->privileges & Privilege::BAN_USER) { send_text_message(c, u"$C6You do not have\nsufficient privileges."); return; } uint64_t usecs = stoull(encode_sjis(args), nullptr, 0) * 1000000; size_t unit_offset = 0; for (; isdigit(args[unit_offset]); unit_offset++); if (args[unit_offset] == 'm') { usecs *= 60; } else if (args[unit_offset] == 'h') { usecs *= 60 * 60; } else if (args[unit_offset] == 'd') { usecs *= 60 * 60 * 24; } else if (args[unit_offset] == 'w') { usecs *= 60 * 60 * 24 * 7; } else if (args[unit_offset] == 'M') { usecs *= 60 * 60 * 24 * 30; } else if (args[unit_offset] == 'y') { usecs *= 60 * 60 * 24 * 365; } // TODO: put the length of time in this message. or don't; presumably the // person deserved it s->license_manager->ban_until(target->license->serial_number, now() + usecs); send_message_box(target, u"$C6You were banned by a moderator."); target->should_disconnect = true; string target_name = name_for_client(target); send_text_message_printf(l, "$C6%s banned", target_name.c_str()); } //////////////////////////////////////////////////////////////////////////////// // Cheat commands static void server_command_warp(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, true); check_cheats_enabled(l); uint32_t area = stoul(encode_sjis(args), nullptr, 0); if (!l->episode || (l->episode > 3)) { return; } if (c->area == area) { return; } if ((l->episode == 1) && (area > 17)) { send_text_message(c, u"$C6Area numbers must be\n17 or less."); return; } if ((l->episode == 2) && (area > 17)) { send_text_message(c, u"$C6Area numbers must be\n17 or less."); return; } if ((l->episode == 3) && (area > 10)) { send_text_message(c, u"$C6Area numbers must be\n10 or less."); return; } send_warp(c, area); } static void proxy_command_warp(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string& args) { uint32_t area = stoul(encode_sjis(args), nullptr, 0); send_warp(session.client_channel, session.lobby_client_id, area); } static void server_command_next(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); check_cheats_enabled(l); if (!l->episode || (l->episode > 3)) { return; } uint8_t new_area = c->area + 1; if (((l->episode == 1) && (new_area > 17)) || ((l->episode == 2) && (new_area > 17)) || ((l->episode == 3) && (new_area > 10))) { new_area = 0; } send_warp(c, new_area); } static void server_command_what(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); if (!l->episode || (l->episode > 3)) { return; } if (!(l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) { send_text_message(c, u"$C4Item tracking is off"); } else { float min_dist2 = 0.0f; uint32_t nearest_item_id = 0xFFFFFFFF; for (const auto& it : l->item_id_to_floor_item) { if (it.second.area != c->area) { continue; } float dx = it.second.x - c->x; float dz = it.second.z - c->z; float dist2 = (dx * dx) + (dz * dz); if ((nearest_item_id == 0xFFFFFFFF) || (dist2 < min_dist2)) { nearest_item_id = it.first; min_dist2 = dist2; } } if (nearest_item_id == 0xFFFFFFFF) { send_text_message(c, u"$C4No items are near you"); } else { const auto& item = l->item_id_to_floor_item.at(nearest_item_id); string name = name_for_item(item.inv_item.data, true); send_text_message(c, decode_sjis(name)); } } } static void server_command_song(shared_ptr, shared_ptr, 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, song); } static void server_command_infinite_hp(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); check_cheats_enabled(l); c->infinite_hp = !c->infinite_hp; send_text_message_printf(c, "$C6Infinite HP %s", c->infinite_hp ? "enabled" : "disabled"); } static void proxy_command_infinite_hp(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string&) { session.infinite_hp = !session.infinite_hp; send_text_message_printf(session.client_channel, "$C6Infinite HP %s", session.infinite_hp ? "enabled" : "disabled"); } static void server_command_infinite_tp(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); check_cheats_enabled(l); c->infinite_tp = !c->infinite_tp; send_text_message_printf(c, "$C6Infinite TP %s", c->infinite_tp ? "enabled" : "disabled"); } static void proxy_command_infinite_tp(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string&) { session.infinite_tp = !session.infinite_tp; send_text_message_printf(session.client_channel, "$C6Infinite TP %s", session.infinite_tp ? "enabled" : "disabled"); } static void server_command_switch_assist(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string&) { check_is_game(l, true); check_cheats_enabled(l); c->switch_assist = !c->switch_assist; send_text_message_printf(c, "$C6Switch assist %s", c->switch_assist ? "enabled" : "disabled"); } static void proxy_command_switch_assist(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string&) { session.switch_assist = !session.switch_assist; send_text_message_printf(session.client_channel, "$C6Switch assist %s", session.switch_assist ? "enabled" : "disabled"); } static void server_command_item(shared_ptr, shared_ptr l, shared_ptr c, const std::u16string& args) { check_is_game(l, true); check_cheats_enabled(l); string data = parse_data_string(encode_sjis(args)); if (data.size() < 2) { send_text_message(c, u"$C6Item codes must be\n2 bytes or more"); return; } if (data.size() > 16) { send_text_message(c, u"$C6Item codes must be\n16 bytes or fewer"); return; } l->next_drop_item.clear(); if (data.size() <= 12) { memcpy(&l->next_drop_item.data.data1, data.data(), data.size()); } else { memcpy(&l->next_drop_item.data.data1, data.data(), 12); memcpy(&l->next_drop_item.data.data2, data.data() + 12, data.size() - 12); } string name = name_for_item(l->next_drop_item.data, true); send_text_message(c, u"$C7Next drop:\n" + decode_sjis(name)); } static void proxy_command_item(shared_ptr, ProxyServer::LinkedSession& session, const std::u16string& args) { if (session.version == GameVersion::BB) { send_text_message(session.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, 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, u"$C6You must be the\nleader to use this\ncommand"); return; } string data = parse_data_string(encode_sjis(args)); if (data.size() < 2) { send_text_message(session.client_channel, u"$C6Item codes must be\n2 bytes or more"); return; } if (data.size() > 16) { send_text_message(session.client_channel, u"$C6Item codes must be\n16 bytes or fewer"); return; } session.next_drop_item.clear(); if (data.size() <= 12) { memcpy(&session.next_drop_item.data.data1, data.data(), data.size()); } else { memcpy(&session.next_drop_item.data.data1, data.data(), 12); memcpy(&session.next_drop_item.data.data2, data.data() + 12, data.size() - 12); } string name = name_for_item(session.next_drop_item.data, true); send_text_message(session.client_channel, u"$C7Next drop:\n" + decode_sjis(name)); } //////////////////////////////////////////////////////////////////////////////// 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); struct ChatCommandDefinition { server_handler_t server_handler; proxy_handler_t proxy_handler; u16string usage; }; static const unordered_map chat_commands({ // TODO: implement command_help and actually use the usage strings here {u"$allevent", {server_command_lobby_event_all, nullptr, u"Usage:\nallevent "}}, {u"$ann", {server_command_announce, nullptr, u"Usage:\nann "}}, {u"$arrow", {server_command_arrow, proxy_command_arrow, u"Usage:\narrow "}}, {u"$ax", {server_command_ax, nullptr, u"Usage:\nax "}}, {u"$ban", {server_command_ban, nullptr, u"Usage:\nban "}}, // TODO: implement this on proxy server {u"$bbchar", {server_command_convert_char_to_bb, nullptr, u"Usage:\nbbchar <1-4>"}}, {u"$cheat", {server_command_cheat, nullptr, u"Usage:\ncheat"}}, {u"$dbgid", {server_command_dbgid, nullptr, u"Usage:\ndbgid"}}, {u"$edit", {server_command_edit, nullptr , u"Usage:\nedit "}}, {u"$event", {server_command_lobby_event, proxy_command_lobby_event, u"Usage:\nevent "}}, {u"$gc", {server_command_get_self_card, proxy_command_get_player_card, u"Usage:\ngc"}}, {u"$infhp", {server_command_infinite_hp, proxy_command_infinite_hp, u"Usage:\ninfhp"}}, {u"$inftp", {server_command_infinite_tp, proxy_command_infinite_tp, u"Usage:\ninftp"}}, {u"$item", {server_command_item, proxy_command_item, u"Usage:\nitem "}}, {u"$kick", {server_command_kick, nullptr, u"Usage:\nkick "}}, {u"$li", {server_command_lobby_info, proxy_command_lobby_info, u"Usage:\nli"}}, {u"$maxlevel", {server_command_max_level, nullptr, u"Usage:\nmax_level "}}, {u"$minlevel", {server_command_min_level, nullptr, u"Usage:\nmin_level "}}, // TODO: implement this on proxy server {u"$next", {server_command_next, nullptr, u"Usage:\nnext"}}, {u"$password", {server_command_password, nullptr, u"Usage:\nlock [password]\nomit password to\nunlock game"}}, {u"$persist", {server_command_persist, nullptr, u"Usage:\npersist"}}, {u"$proxygc", {server_command_proxygc, proxy_command_proxygc, u"Usage:\nproxygc "}}, {u"$rand", {server_command_rand, proxy_command_rand, u"Usage:\nrand [hex seed]\nomit seed to revert\nto default"}}, {u"$secid", {server_command_secid, proxy_command_secid, u"Usage:\nsecid [section ID]\nomit section ID to\nrevert to normal"}}, {u"$silence", {server_command_silence, nullptr, u"Usage:\nsilence "}}, // TODO: implement this on proxy server {u"$song", {server_command_song, nullptr, u"Usage:\nsong "}}, {u"$swa", {server_command_switch_assist, proxy_command_switch_assist, u"Usage:\nswa"}}, {u"$type", {server_command_lobby_type, nullptr, u"Usage:\ntype "}}, {u"$warp", {server_command_warp, proxy_command_warp, u"Usage:\nwarp "}}, {u"$what", {server_command_what, nullptr, u"Usage:\nwhat"}}, }); struct SplitCommand { u16string name; u16string args; SplitCommand(const u16string& text) { size_t space_pos = text.find(u' '); if (space_pos != string::npos) { this->name = text.substr(0, space_pos); this->args = text.substr(space_pos + 1); } else { this->name = text; } } }; // 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) { SplitCommand cmd(text); const ChatCommandDefinition* def = nullptr; try { def = &chat_commands.at(cmd.name); } catch (const out_of_range&) { send_text_message(c, u"$C6Unknown command"); return; } if (!def->server_handler) { send_text_message(c, u"$C6Command not available\non game server"); } else { try { def->server_handler(s, l, c, cmd.args); } catch (const precondition_failed& e) { send_text_message(c, e.what()); } catch (const exception& e) { send_text_message_printf(c, "$C6Failed:\n%s", e.what()); } } } void on_chat_command(std::shared_ptr s, ProxyServer::LinkedSession& session, 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"); return; } if (!def->proxy_handler) { send_text_message(session.client_channel, u"$C6Command not available\non proxy server"); } else { try { def->proxy_handler(s, session, cmd.args); } catch (const precondition_failed& e) { send_text_message(session.client_channel, e.what()); } catch (const exception& e) { send_text_message_printf(session.client_channel, "$C6Failed:\n%s", e.what()); } } }