diff --git a/ChatCommands.cc b/ChatCommands.cc index 61ba3705..b50cbd79 100644 --- a/ChatCommands.cc +++ b/ChatCommands.cc @@ -17,9 +17,9 @@ using namespace std; //////////////////////////////////////////////////////////////////////////////// -vector section_id_to_name({ - u"Viridia", u"Greennill", u"Skyly", u"Bluefull", u"Purplenum", u"Pinkal", - u"Redria", u"Oran", u"Yellowboze", u"Whitill"}); +vector section_id_to_name({ + "Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", + "Oran", "Yellowboze", "Whitill"}); unordered_map name_to_section_id({ {u"viridia", 0}, @@ -35,53 +35,53 @@ unordered_map name_to_section_id({ vector lobby_event_to_name({ u"none", u"xmas", u"none", u"val", u"easter", u"hallo", u"sonic", - u"newyear", u"spring", u"white", u"wedding", u"fall", u"s-summer", - u"s-spring", u"summer"}); + u"newyear", u"summer", u"white", u"wedding", u"fall", u"s-spring", + u"s-summer", u"spring"}); unordered_map name_to_lobby_event({ - {u"none", 0}, - {u"xmas", 1}, - {u"val", 3}, - {u"easter", 4}, - {u"hallo", 5}, - {u"sonic", 6}, - {u"newyear", 7}, - {u"spring", 8}, - {u"white", 9}, - {u"wedding", 10}, - {u"fall", 11}, - {u"s-summer", 12}, - {u"s-spring", 13}, - {u"summer", 14}, + {u"none", 0}, + {u"xmas", 1}, + {u"val", 3}, + {u"easter", 4}, + {u"hallo", 5}, + {u"sonic", 6}, + {u"newyear", 7}, + {u"summer", 8}, + {u"white", 9}, + {u"wedding", 10}, + {u"fall", 11}, + {u"s-spring", 12}, + {u"s-summer", 13}, + {u"spring", 14}, }); unordered_map lobby_type_to_name({ - {0x00, u"normal"}, - {0x0F, u"inormal"}, - {0x10, u"ipc"}, - {0x11, u"iball"}, - {0x67, u"cave2u"}, - {0xD4, u"cave1"}, - {0xE9, u"planet"}, - {0xEA, u"clouds"}, - {0xED, u"cave"}, - {0xEE, u"jungle"}, - {0xEF, u"forest2-2"}, - {0xF0, u"forest2-1"}, - {0xF1, u"windpower"}, - {0xF2, u"overview"}, - {0xF3, u"seaside"}, - {0xF4, u"some?"}, - {0xF5, u"dmorgue"}, - {0xF6, u"caelum"}, - {0xF8, u"digital"}, - {0xF9, u"boss1"}, - {0xFA, u"boss2"}, - {0xFB, u"boss3"}, - {0xFC, u"dragon"}, - {0xFD, u"derolle"}, - {0xFE, u"volopt"}, - {0xFF, u"darkfalz"}, + {0x00, u"normal"}, + {0x0F, u"inormal"}, + {0x10, u"ipc"}, + {0x11, u"iball"}, + {0x67, u"cave2u"}, + {0xD4, u"cave1"}, + {0xE9, u"planet"}, + {0xEA, u"clouds"}, + {0xED, u"cave"}, + {0xEE, u"jungle"}, + {0xEF, u"forest2-2"}, + {0xF0, u"forest2-1"}, + {0xF1, u"windpower"}, + {0xF2, u"overview"}, + {0xF3, u"seaside"}, + {0xF4, u"some?"}, + {0xF5, u"dmorgue"}, + {0xF6, u"caelum"}, + {0xF8, u"digital"}, + {0xF9, u"boss1"}, + {0xFA, u"boss2"}, + {0xFB, u"boss3"}, + {0xFC, u"dragon"}, + {0xFD, u"derolle"}, + {0xFE, u"volopt"}, + {0xFF, u"darkfalz"}, }); unordered_map name_to_lobby_type({ @@ -164,42 +164,57 @@ unordered_map name_to_npc_id({ +class precondition_failed { +public: + precondition_failed(const char16_t* user_msg) : user_msg(user_msg) { } + ~precondition_failed() = default; + + const char16_t* what() const { + return this->user_msg; + } + +private: + const char16_t* user_msg; +}; + static void check_privileges(shared_ptr c, uint64_t mask) { if (!c->license) { - throw runtime_error("not logged in"); + throw precondition_failed(u"$C6You are not\nlogged in."); } if ((c->license->privileges & mask) != mask) { - throw runtime_error("insufficient permissions"); + 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 runtime_error("incorrect 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 runtime_error("incorrect 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 runtime_error(is_game ? "can only be used in games" : "can only be used in lobbies"); + throw precondition_failed(is_game ? + u"$C6This command cannot\nbe used in lobbies." : + u"$C6This command cannot\nbe used in games."); } } static void check_cheats_enabled(shared_ptr l) { if (!(l->flags & LobbyFlag::CheatsEnabled)) { - throw runtime_error("can only be used in cheat mode"); + 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 runtime_error("you are not the game leader"); + throw precondition_failed(u"$C6This command can\nonly be used by\nthe game leader."); } } @@ -212,8 +227,10 @@ static void command_lobby_info(shared_ptr s, shared_ptr l, shared_ptr c, const char16_t* args) { // no preconditions - everyone can use this command - string buffer; - if (l->is_game()) { + 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); @@ -221,15 +238,17 @@ static void command_lobby_info(shared_ptr s, shared_ptr l, level_string = string_printf("Levels: %d-%d", l->min_level + 1, l->max_level + 1); } - send_text_message_printf(c, "$C6Lobby ID: %08X\n%s\nSection ID: %s\nCheat mode: %s", + send_text_message_printf(c, "$C6Game ID: %08X\n%s\nSection ID: %s\nCheat mode: %s", l->lobby_id, level_string.c_str(), section_id_to_name.at(l->section_id).c_str(), - (l->flags & LobbyFlag::CheatsEnabled) ? u"on" : u"off"); + (l->flags & LobbyFlag::CheatsEnabled) ? "on" : "off"); } else { - send_text_message_printf(c, "$C6Lobby ID: %08X", l->lobby_id); + 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 command_ax(shared_ptr s, shared_ptr l, @@ -264,7 +283,7 @@ static void command_cheat(shared_ptr s, shared_ptr l, l->flags ^= LobbyFlag::CheatsEnabled; send_text_message_printf(l, "Cheat mode %s", - (l->flags & LobbyFlag::CheatsEnabled) ? u"enabled" : u"disabled"); + (l->flags & LobbyFlag::CheatsEnabled) ? "enabled" : "disabled"); // if cheat mode was disabled, turn off all the cheat features that were on if (!(l->flags & LobbyFlag::CheatsEnabled)) { @@ -286,9 +305,17 @@ static void command_lobby_event(shared_ptr s, shared_ptr l, check_is_game(l, false); check_privileges(c, Privilege::ChangeEvent); + uint8_t new_event; + try { + new_event = name_to_lobby_event.at(args); + } catch (const out_of_range& e) { + send_text_message(c, u"$C6No such lobby event."); + return; + } + { rw_guard g(l->lock, true); - l->event = name_to_lobby_event.at(args); + l->event = new_event; } send_command(l, 0xDA, l->event, NULL, 0); } @@ -297,7 +324,13 @@ static void command_lobby_event_all(shared_ptr s, shared_ptr shared_ptr c, const char16_t* args) { check_privileges(c, Privilege::ChangeEvent); - uint8_t event = name_to_lobby_event.at(args); + uint8_t new_event; + try { + new_event = name_to_lobby_event.at(args); + } catch (const out_of_range& e) { + send_text_message(c, u"$C6No such lobby event."); + return; + } for (auto l : s->all_lobbies()) { if (l->is_game() || !(l->flags & LobbyFlag::Default)) { @@ -306,9 +339,9 @@ static void command_lobby_event_all(shared_ptr s, shared_ptr { rw_guard g(l->lock, true); - l->event = event; + l->event = new_event; } - send_command(l, 0xDA, event, NULL, 0); + send_command(l, 0xDA, new_event, NULL, 0); } } @@ -317,9 +350,17 @@ static void command_lobby_type(shared_ptr s, shared_ptr l, check_is_game(l, false); check_privileges(c, Privilege::ChangeEvent); + uint8_t new_type; + try { + new_type = name_to_lobby_type.at(args); + } catch (const out_of_range& e) { + send_text_message(c, u"$C6No such lobby type."); + return; + } + { rw_guard g(l->lock, true); - l->type = name_to_lobby_type.at(args); + l->type = new_type; if (l->type < ((l->flags & LobbyFlag::Episode3) ? 20 : 15)) { l->type = l->block - 1; } @@ -348,7 +389,7 @@ static void command_password(shared_ptr s, shared_ptr l, } else { char16cpy(l->password, args, 0x10); auto encoded = encode_sjis(l->password); - send_text_message_printf(l, "$C6Game locked with password:\n%s", + send_text_message_printf(l, "$C6Game password:\n%s", encoded.c_str()); } } @@ -426,7 +467,12 @@ static void command_edit(shared_ptr s, shared_ptr l, } else if (tokens[0] == "namecolor") { sscanf(tokens[1].c_str(), "%8X", &c->player.disp.name_color); } else if (tokens[0] == "secid") { - c->player.disp.section_id = name_to_section_id.at(decode_sjis(tokens[1])); + try { + c->player.disp.section_id = name_to_section_id.at(decode_sjis(tokens[1])); + } catch (const out_of_range&) { + send_text_message(c, u"$C6No such section ID."); + return; + } } else if (tokens[0] == "name") { decode_sjis(c->player.disp.name, tokens[1].c_str(), 0x10); add_language_marker_inplace(c->player.disp.name, u'J', 0x10); @@ -435,7 +481,12 @@ static void command_edit(shared_ptr s, shared_ptr l, c->player.disp.extra_model = 0; c->player.disp.v2_flags &= 0xFD; } else { - c->player.disp.extra_model = name_to_npc_id.at(decode_sjis(tokens[1])); + try { + c->player.disp.extra_model = name_to_npc_id.at(decode_sjis(tokens[1])); + } catch (const out_of_range&) { + send_text_message(c, u"$C6No such NPC."); + return; + } c->player.disp.v2_flags |= 0x02; } } else if ((tokens[0] == "tech") && (tokens.size() > 2)) { @@ -445,11 +496,16 @@ static void command_edit(shared_ptr s, shared_ptr l, c->player.disp.technique_levels[x] = level; } } else { - uint8_t tech_id = name_to_tech_id.at(decode_sjis(tokens[1])); - c->player.disp.technique_levels[tech_id] = level; + try { + uint8_t tech_id = name_to_tech_id.at(decode_sjis(tokens[1])); + c->player.disp.technique_levels[tech_id] = level; + } catch (const out_of_range&) { + send_text_message(c, u"$C6No such technique."); + return; + } } } else { - send_text_message(c, u"$C6Unknown field"); + send_text_message(c, u"$C6Unknown field."); return; } @@ -711,9 +767,14 @@ static const unordered_map chat_commands({ void process_chat_command(std::shared_ptr s, std::shared_ptr l, std::shared_ptr c, const char16_t* text) { + // remove the chat command marker + if (text[0] == u'$') { + text++; + } + u16string command_name; u16string text_str(text); - size_t space_pos = text_str.find(L' '); + size_t space_pos = text_str.find(u' '); if (space_pos != string::npos) { command_name = text_str.substr(0, space_pos); text_str = text_str.substr(space_pos + 1); @@ -732,8 +793,9 @@ void process_chat_command(std::shared_ptr s, std::shared_ptr try { def->handler(s, l, c, text_str.c_str()); + } 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()); - return; } } diff --git a/Lobby.cc b/Lobby.cc index 44b6d687..fd80c55b 100644 --- a/Lobby.cc +++ b/Lobby.cc @@ -22,7 +22,7 @@ Lobby::Lobby() : lobby_id(0), min_level(0), max_level(0xFFFFFFFF), } bool Lobby::is_game() const { - return (this->lobby_id < 0); + return this->flags & LobbyFlag::IsGame; } void Lobby::reassign_leader_on_client_departure_locked(size_t leaving_client_index) { @@ -106,7 +106,13 @@ void Lobby::remove_client_locked(shared_ptr c) { } this->clients[c->lobby_client_id] = NULL; - c->lobby_id = 0; + + // 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; + } this->reassign_leader_on_client_departure_locked(c->lobby_client_id); } diff --git a/Lobby.hh b/Lobby.hh index 52833ad2..1eb7574d 100644 --- a/Lobby.hh +++ b/Lobby.hh @@ -25,7 +25,7 @@ enum LobbyFlag { struct Lobby { mutable rw_lock lock; - uint64_t lobby_id; + uint32_t lobby_id; uint32_t min_level; uint32_t max_level; diff --git a/Quest.cc b/Quest.cc index 55a34563..9702dfe1 100644 --- a/Quest.cc +++ b/Quest.cc @@ -34,8 +34,12 @@ const char* name_for_category(QuestCategory category) { return "VR"; case QuestCategory::Tower: return "Tower"; - case QuestCategory::Government: - return "Government"; + case QuestCategory::GovernmentEpisode1: + return "GovernmentEpisode1"; + case QuestCategory::GovernmentEpisode2: + return "GovernmentEpisode2"; + case QuestCategory::GovernmentEpisode4: + return "GovernmentEpisode4"; case QuestCategory::Download: return "Download"; case QuestCategory::Battle: @@ -170,18 +174,29 @@ Quest::Quest(const string& bin_filename) : quest_id(-1), // get the category from the second token if needed if (this->category == QuestCategory::Unknown) { - static const unordered_map name_to_category({ - {"ret", QuestCategory::Retrieval}, - {"ext", QuestCategory::Extermination}, - {"evt", QuestCategory::Event}, - {"shp", QuestCategory::Shop}, - {"vr", QuestCategory::VR}, - {"twr", QuestCategory::Tower}, - {"gov", QuestCategory::Government}, - {"dl", QuestCategory::Download}, - {"1p", QuestCategory::Solo}, - }); - this->category = name_to_category.at(tokens[1]); + if (tokens[1] == "gov") { + if (this->episode == 0) { + this->category = QuestCategory::GovernmentEpisode1; + } else if (this->episode == 1) { + this->category = QuestCategory::GovernmentEpisode2; + } else if (this->episode == 2) { + this->category = QuestCategory::GovernmentEpisode4; + } else { + throw invalid_argument("government quest has incorrect episode"); + } + } else { + static const unordered_map name_to_category({ + {"ret", QuestCategory::Retrieval}, + {"ext", QuestCategory::Extermination}, + {"evt", QuestCategory::Event}, + {"shp", QuestCategory::Shop}, + {"vr", QuestCategory::VR}, + {"twr", QuestCategory::Tower}, + {"dl", QuestCategory::Download}, + {"1p", QuestCategory::Solo}, + }); + this->category = name_to_category.at(tokens[1]); + } tokens.erase(tokens.begin() + 1); } @@ -299,10 +314,10 @@ shared_ptr Quest::bin_contents() const { } shared_ptr Quest::dat_contents() const { - if (!this->bin_contents_ptr) { - this->bin_contents_ptr.reset(new string(load_file(this->file_basename + ".dat"))); + if (!this->dat_contents_ptr) { + this->dat_contents_ptr.reset(new string(load_file(this->file_basename + ".dat"))); } - return this->bin_contents_ptr; + return this->dat_contents_ptr; } diff --git a/Quest.hh b/Quest.hh index 73c649ac..909a453d 100644 --- a/Quest.hh +++ b/Quest.hh @@ -17,7 +17,9 @@ enum class QuestCategory { Shop, VR, Tower, - Government, + GovernmentEpisode1, + GovernmentEpisode2, + GovernmentEpisode4, Download, Battle, Challenge, diff --git a/ReceiveCommands.cc b/ReceiveCommands.cc index d53e467d..5a87e515 100644 --- a/ReceiveCommands.cc +++ b/ReceiveCommands.cc @@ -530,6 +530,20 @@ void process_menu_item_info_request(shared_ptr s, shared_ptrquest_index) { + send_quest_info(c, u"$C6Quests are not available."); + break; + } + auto q = s->quest_index->get(c->version, cmd->item_id); + if (!q) { + send_quest_info(c, u"$C6Quest does not exist."); + break; + } + send_quest_info(c, q->long_description.c_str()); + break; + } + default: send_ship_info(c, u"Incorrect menu ID."); break; @@ -678,7 +692,7 @@ void process_menu_selection(shared_ptr s, shared_ptr c, auto l = s->find_lobby(c->lobby_id); auto quests = s->quest_index->filter(c->version, c->flags & ClientFlag::IsDCv1, - static_cast(cmd->item_id), l->episode); + static_cast(cmd->item_id & 0xFF), l->episode - 1); if (quests.empty()) { send_lobby_message_box(c, u"$C6There are no quests\navailable in that\ncategory."); break; @@ -794,40 +808,40 @@ void process_change_block(shared_ptr s, shared_ptr c, // Quest commands vector quest_categories_menu({ - MenuItem(0x01000000, u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0), - MenuItem(0x01000001, u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0), - MenuItem(0x01000002, u"Events", u"$E$C6Quests that are part\nof an event", 0), - MenuItem(0x01000003, u"Shops", u"$E$C6Quests that contain\nshops", 0), - MenuItem(0x01000004, u"Virtual Reality", u"$E$C6Quests that are\ndone in a simulator", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), - MenuItem(0x01000005, u"Control Tower", u"$E$C6Quests that take\nplace at the Control\nTower", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), + MenuItem(static_cast(QuestCategory::Retrieval), u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0), + MenuItem(static_cast(QuestCategory::Extermination), u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0), + MenuItem(static_cast(QuestCategory::Event), u"Events", u"$E$C6Quests that are part\nof an event", 0), + MenuItem(static_cast(QuestCategory::Shop), u"Shops", u"$E$C6Quests that contain\nshops", 0), + MenuItem(static_cast(QuestCategory::VR), u"Virtual Reality", u"$E$C6Quests that are\ndone in a simulator", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), + MenuItem(static_cast(QuestCategory::Tower), u"Control Tower", u"$E$C6Quests that take\nplace at the Control\nTower", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), }); vector quest_battle_menu({ - MenuItem(0x01000100, u"Battle", u"$E$C6Battle mode rule\nsets", 0), + MenuItem(static_cast(QuestCategory::Battle), u"Battle", u"$E$C6Battle mode rule\nsets", 0), }); vector quest_challenge_menu({ - MenuItem(0x01000200, u"Challenge", u"$E$C6Challenge mode\nquests", 0), + MenuItem(static_cast(QuestCategory::Challenge), u"Challenge", u"$E$C6Challenge mode\nquests", 0), }); vector quest_solo_menu({ - MenuItem(0x01000200, u"Solo Quests", u"$E$C6Quests that require\na single player", 0), + MenuItem(static_cast(QuestCategory::Solo), u"Solo Quests", u"$E$C6Quests that require\na single player", 0), }); vector quest_government_menu({ - MenuItem(0x01000300, u"Hero in Red",u"$E$CG-Red Ring Rico-\n$C6Quests that follow\nthe Episode 1\nstoryline", 0), - MenuItem(0x01000301, u"The Military's Hero",u"$E$CG-Heathcliff Flowen-\n$C6Quests that follow\nthe Episode 2\nstoryline", 0), - MenuItem(0x01000302, u"The Meteor Impact Incident", u"$E$C6Quests that follow\nthe Episode 4\nstoryline", 0), + MenuItem(static_cast(QuestCategory::GovernmentEpisode1), u"Hero in Red",u"$E$CG-Red Ring Rico-\n$C6Quests that follow\nthe Episode 1\nstoryline", 0), + MenuItem(static_cast(QuestCategory::GovernmentEpisode2), u"The Military's Hero",u"$E$CG-Heathcliff Flowen-\n$C6Quests that follow\nthe Episode 2\nstoryline", 0), + MenuItem(static_cast(QuestCategory::GovernmentEpisode4), u"The Meteor Impact Incident", u"$E$C6Quests that follow\nthe Episode 4\nstoryline", 0), }); vector quest_download_menu({ - MenuItem(0x01000400, u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0), - MenuItem(0x01000401, u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0), - MenuItem(0x01000402, u"Events", u"$E$C6Quests that are part\nof an event", 0), - MenuItem(0x01000403, u"Shops", u"$E$C6Quests that contain\nshops", 0), - MenuItem(0x01000404, u"Virtual Reality", u"$E$C6Quests that are\ndone in a simulator", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), - MenuItem(0x01000405, u"Control Tower", u"$E$C6Quests that take\nplace at the Control\nTower", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), - MenuItem(0x01000406, u"Download", u"$E$C6Quests to download\nto your Memory Card", 0), + MenuItem(static_cast(QuestCategory::Retrieval), u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0), + MenuItem(static_cast(QuestCategory::Extermination), u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0), + MenuItem(static_cast(QuestCategory::Event), u"Events", u"$E$C6Quests that are part\nof an event", 0), + MenuItem(static_cast(QuestCategory::Shop), u"Shops", u"$E$C6Quests that contain\nshops", 0), + MenuItem(static_cast(QuestCategory::VR), u"Virtual Reality", u"$E$C6Quests that are\ndone in a simulator", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), + MenuItem(static_cast(QuestCategory::Tower), u"Control Tower", u"$E$C6Quests that take\nplace at the Control\nTower", MenuItemFlag::InvisibleOnDC | MenuItemFlag::InvisibleOnPC), + MenuItem(static_cast(QuestCategory::Download), u"Download", u"$E$C6Quests to download\nto your Memory Card", 0), }); void process_quest_list_request(shared_ptr s, shared_ptr c, @@ -874,6 +888,11 @@ void process_quest_ready(shared_ptr s, shared_ptr c, return; } + { + rw_guard g(c->lock, true); + c->flags &= ~ClientFlag::Loading; + } + // check if any client is still loading // TODO: we need to handle clients disconnecting while loading. probably // process_client_disconnect needs to check for this case or something @@ -956,15 +975,10 @@ void process_player_data(shared_ptr s, shared_ptr c, c->pending_bb_save_username.clear(); } - // if it's 61 and the client isn't in a lobby, add them to an available lobby - if ((command == 0x61) && !c->lobby_id && - (c->server_behavior == ServerBehavior::LobbyServer)) { + // if the client isn't in a lobby, add them to an available lobby + if (!c->lobby_id && (c->server_behavior == ServerBehavior::LobbyServer)) { s->add_client_to_available_lobby(c); } - - if (command == 0x98) { - s->change_client_lobby(c, NULL); - } } //////////////////////////////////////////////////////////////////////////////// @@ -1002,7 +1016,7 @@ void process_chat_generic(shared_ptr s, shared_ptr c, if (processed_text[0] == L'$') { auto l = s->find_lobby(c->lobby_id); if (l) { - process_chat_command(s, l, c, &text[1]); + process_chat_command(s, l, c, &processed_text[1]); } } else { if (!c->can_chat) { @@ -1533,16 +1547,8 @@ void process_create_game_bb(shared_ptr s, shared_ptr c, check_size(size, sizeof(Cmd)); const auto* cmd = reinterpret_cast(data); - uint8_t episode = cmd->episode; - if (c->version == GameVersion::DC) { - episode = 1; - } - if (c->flags & ClientFlag::Episode3Games) { - episode = 0xFF; - } - auto game = create_game_generic(s, c, cmd->name, cmd->password, - episode, cmd->difficulty, cmd->battle_mode, cmd->challenge_mode, + cmd->episode, cmd->difficulty, cmd->battle_mode, cmd->challenge_mode, cmd->solo_mode); s->add_lobby(game); diff --git a/ReceiveSubcommands.cc b/ReceiveSubcommands.cc index d1abf857..b7bbca3f 100644 --- a/ReceiveSubcommands.cc +++ b/ReceiveSubcommands.cc @@ -184,9 +184,9 @@ static void process_subcommand_use_technique(shared_ptr s, static void process_subcommand_drop_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 6); - if (l->version == GameVersion::BB) { + check_size(count, 6); + struct Cmd { uint8_t command; uint8_t size; @@ -221,9 +221,9 @@ static void process_subcommand_drop_item(shared_ptr s, static void process_subcommand_drop_stacked_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 6); - if (l->version == GameVersion::BB) { + check_size(count, 6); + struct Cmd { uint8_t command; uint8_t size; @@ -268,9 +268,9 @@ static void process_subcommand_drop_stacked_item(shared_ptr s, static void process_subcommand_pick_up_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 3); - if (l->version == GameVersion::BB) { + check_size(count, 3); + struct Cmd { uint8_t command; uint8_t size; @@ -305,9 +305,9 @@ static void process_subcommand_pick_up_item(shared_ptr s, static void process_subcommand_equip_unequip_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 3); - if (l->version == GameVersion::BB) { + check_size(count, 3); + auto* cmd = reinterpret_cast(p); if ((cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) { return; @@ -326,13 +326,12 @@ static void process_subcommand_equip_unequip_item(shared_ptr s, } } -// player uses an item (see ClientUseItem for specific item handlers) static void process_subcommand_use_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 2); - if (l->version == GameVersion::BB) { + check_size(count, 2); + auto* cmd = reinterpret_cast(p); if ((cmd->size != 2) || (cmd->client_id != c->lobby_client_id)) { return; @@ -352,7 +351,6 @@ static void process_subcommand_use_item(shared_ptr s, forward_subcommand(l, c, command, flag, p, count); } -// player opens the bank window static void process_subcommand_open_bank(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { @@ -365,9 +363,9 @@ static void process_subcommand_open_bank(shared_ptr s, static void process_subcommand_bank_action(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 4); - if (l->version == GameVersion::BB) { + check_size(count, 4); + struct Cmd { uint8_t subcommand; uint8_t size; @@ -427,9 +425,9 @@ static void process_subcommand_bank_action(shared_ptr s, static void process_subcommand_sort_inventory(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 31); - if (l->version == GameVersion::BB) { + check_size(count, 31); + struct Cmd { uint8_t command; uint8_t size; @@ -470,9 +468,9 @@ static void process_subcommand_sort_inventory(shared_ptr s, static void process_subcommand_enemy_drop_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 6); - if (l->version == GameVersion::BB) { + check_size(count, 6); + struct Cmd { uint8_t command; uint8_t size; @@ -532,9 +530,9 @@ static void process_subcommand_enemy_drop_item(shared_ptr s, static void process_subcommand_box_drop_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 10); - if (l->version == GameVersion::BB) { + check_size(count, 10); + struct Cmd { uint8_t command; uint8_t size; @@ -601,9 +599,9 @@ static void process_subcommand_box_drop_item(shared_ptr s, static void process_subcommand_monster_hit(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 10); - if (l->version == GameVersion::BB) { + check_size(count, 10); + struct Cmd { uint8_t command; uint8_t size; @@ -637,7 +635,9 @@ static void process_subcommand_monster_hit(shared_ptr s, static void process_subcommand_monster_killed(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 3); + if (l->version == GameVersion::BB) { + check_size(count, 3); + } forward_subcommand(l, c, command, flag, p, count); @@ -713,9 +713,9 @@ static void process_subcommand_monster_killed(shared_ptr s, static void process_subcommand_destroy_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 3); - if (l->version == GameVersion::BB) { + check_size(count, 3); + auto* cmd = reinterpret_cast(p); if ((cmd->size != 3) || !l->is_game()) { return; @@ -730,9 +730,9 @@ static void process_subcommand_destroy_item(shared_ptr s, static void process_subcommand_identify_item(shared_ptr s, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const PSOSubcommand* p, size_t count) { - check_size(count, 3); - if (l->version == GameVersion::BB) { + check_size(count, 3); + auto* cmd = reinterpret_cast(p); if (!l->is_game() || (cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) { return; @@ -767,9 +767,10 @@ static void process_subcommand_identify_item(shared_ptr s, // static void process_subcommand_accept_identified_item(shared_ptr s, // shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, // const PSOSubcommand* p, size_t count) { -// check_size(count, 3); // // if (l->version == GameVersion::BB) { +// check_size(count, 3); +// // auto* cmd = reinterpret_cast(p); // if ((cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) { // return; diff --git a/SendCommands.cc b/SendCommands.cc index 25e2e3d2..0e891016 100644 --- a/SendCommands.cc +++ b/SendCommands.cc @@ -523,7 +523,7 @@ void send_chat_message(shared_ptr c, uint32_t from_serial_number, if (c->version == GameVersion::BB) { data.append(u"\x09J"); } - data.append(from_name); + data.append(remove_language_marker(from_name)); data.append(u"\x09\x09J"); data.append(text); send_large_message(c, 0x06, data.c_str(), from_serial_number, true); @@ -1090,6 +1090,7 @@ static void send_quest_menu_pc(shared_ptr c, uint32_t menu_id, e.quest_id = quest->quest_id; char16cpy(e.name, quest->name.c_str(), 0x20); char16cpy(e.short_desc, quest->short_description.c_str(), 0x70); + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); @@ -1112,6 +1113,7 @@ static void send_quest_menu_pc(std::shared_ptr c, uint32_t menu_id, e.item_id = item.item_id; char16cpy(e.name, item.name.c_str(), 0x20); char16cpy(e.short_desc, item.description.c_str(), 0x70); + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); @@ -1134,6 +1136,7 @@ static void send_quest_menu_gc(shared_ptr c, uint32_t menu_id, e.quest_id = quest->quest_id; encode_sjis(e.name, quest->name.c_str(), 0x20); encode_sjis(e.short_desc, quest->short_description.c_str(), 0x70); + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); @@ -1156,6 +1159,7 @@ static void send_quest_menu_gc(shared_ptr c, uint32_t menu_id, e.item_id = item.item_id; encode_sjis(e.name, item.name.c_str(), 0x20); encode_sjis(e.short_desc, item.description.c_str(), 0x70); + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); @@ -1180,6 +1184,7 @@ static void send_quest_menu_bb(shared_ptr c, uint32_t menu_id, e.quest_id = quest->quest_id; char16cpy(e.name, quest->name.c_str(), 0x20); char16cpy(e.short_desc, quest->short_description.c_str(), 0x7A); + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); @@ -1204,6 +1209,7 @@ static void send_quest_menu_bb(shared_ptr c, uint32_t menu_id, e.item_id = item.item_id; char16cpy(e.name, item.name.c_str(), 0x20); char16cpy(e.short_desc, item.description.c_str(), 0x7A); + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); @@ -1488,7 +1494,17 @@ static void send_join_lobby_pc(shared_ptr c, shared_ptr l) { static void send_join_lobby_gc(shared_ptr c, shared_ptr l) { rw_guard g(l->lock, false); - uint8_t lobby_type = (l->type > 14) ? (l->block - 1) : l->type; + uint8_t lobby_type = l->type; + if (c->flags & ClientFlag::Episode3Games) { + if ((l->type > 0x14) && (l->type < 0xE9)) { + lobby_type = l->block - 1; + } + } else { + if ((l->type > 0x11) && (l->type != 0x67) && (l->type != 0xD4) && (l->type < 0xFC)) { + lobby_type = l->block - 1; + } + } + struct { uint8_t client_id; uint8_t leader_id; diff --git a/SendCommands.hh b/SendCommands.hh index 88c2ef1f..5b53222f 100644 --- a/SendCommands.hh +++ b/SendCommands.hh @@ -23,10 +23,10 @@ #define QUEST_MENU_ID 0x7F02CA94 #define QUEST_FILTER_MENU_ID 0xC38CA039 -#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF #define MAIN_MENU_GO_TO_LOBBY 0x00000001 #define MAIN_MENU_INFORMATION 0x00000002 #define MAIN_MENU_DISCONNECT 0x00000003 +#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF diff --git a/Server.cc b/Server.cc index e0e38de6..5af2c86b 100644 --- a/Server.cc +++ b/Server.cc @@ -212,14 +212,14 @@ void Server::receive_and_process_commands(shared_ptr c, struct buffereve // to call string functions on the buffer in command handlers string data = c->recv_buffer.substr(offset + header_size, size - header_size); data.append(4, '\0'); - try { + // try { process_command(this->state, c, header->command(c->version), header->flag(c->version), size - header_size, data.data()); - } catch (const exception& e) { - log(INFO, "[Server] error in client stream: %s", e.what()); - c->should_disconnect = true; - return; - } + // } catch (const exception& e) { + // log(INFO, "[Server] error in client stream: %s", e.what()); + // c->should_disconnect = true; + // return; + // } // BB pads commands to 8-byte boundaries, so if we see a shorter command, // skip over the padding diff --git a/ServerState.cc b/ServerState.cc index 8e13eec9..28946340 100644 --- a/ServerState.cc +++ b/ServerState.cc @@ -10,8 +10,7 @@ using namespace std; -ServerState::ServerState() : run_dns_server(true), next_lobby_id(1), - next_game_id(-1) { +ServerState::ServerState() : run_dns_server(true), next_lobby_id(1) { this->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby", u"Join the lobby.", 0); this->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information", @@ -19,14 +18,14 @@ ServerState::ServerState() : run_dns_server(true), next_lobby_id(1), this->main_menu.emplace_back(MAIN_MENU_DISCONNECT, u"Disconnect", u"Disconnect.", 0); - for (size_t x = 0; x < 15; x++) { + for (size_t x = 0; x < 20; x++) { + auto lobby_name = decode_sjis(string_printf("LOBBY%zu", x + 1)); shared_ptr l(new Lobby()); - l->flags |= LobbyFlag::Public | LobbyFlag::Default; - this->add_lobby(l); - } - for (size_t x = 0; x < 5; x++) { - shared_ptr l(new Lobby()); - l->flags |= LobbyFlag::Public | LobbyFlag::Default | LobbyFlag::Episode3; + l->flags |= LobbyFlag::Public | LobbyFlag::Default | ((x > 14) ? LobbyFlag::Episode3 : 0); + l->block = x + 1; + l->type = x; + char16cpy(l->name, lobby_name.c_str(), 0x24); + l->max_clients = 12; this->add_lobby(l); } } @@ -97,16 +96,11 @@ void ServerState::send_lobby_join_notifications(shared_ptr l, } } -shared_ptr ServerState::find_lobby(int64_t lobby_id) { +shared_ptr ServerState::find_lobby(uint32_t lobby_id) { rw_guard g(this->lobbies_lock, false); return this->id_to_lobby.at(lobby_id); } -shared_ptr ServerState::find_lobby(const u16string& name) { - rw_guard g(this->lobbies_lock, false); - return this->name_to_lobby.at(name); -} - vector> ServerState::all_lobbies() { rw_guard g(this->lobbies_lock, false); vector> ret; @@ -117,34 +111,23 @@ vector> ServerState::all_lobbies() { } void ServerState::add_lobby(shared_ptr l) { - if (l->is_game()) { - l->lobby_id = this->next_game_id--; - } else { - l->lobby_id = this->next_lobby_id++; - } + l->lobby_id = this->next_lobby_id++; rw_guard g(this->lobbies_lock, true); if (this->id_to_lobby.count(l->lobby_id)) { throw logic_error("lobby already exists with the given id"); } - if (this->name_to_lobby.count(l->name)) { - throw invalid_argument("lobby already exists with the given name"); - } + + log(INFO, "creating lobby %" PRId64, l->lobby_id); this->id_to_lobby.emplace(l->lobby_id, l); - if (l->name[0]) { - this->name_to_lobby.emplace(l->name, l); - } } -void ServerState::remove_lobby(int64_t lobby_id) { +void ServerState::remove_lobby(uint32_t lobby_id) { rw_guard g(this->lobbies_lock, true); auto it = this->id_to_lobby.find(lobby_id); if (it == this->id_to_lobby.end()) { return; } - if (it->second->name[0]) { - this->name_to_lobby.erase(it->second->name); - } this->id_to_lobby.erase(it); } diff --git a/ServerState.hh b/ServerState.hh index 469bf39b..de2e5435 100644 --- a/ServerState.hh +++ b/ServerState.hh @@ -28,6 +28,7 @@ struct PortConfiguration { struct ServerState { std::u16string name; std::unordered_map port_configuration; + bool run_dns_server; std::shared_ptr quest_index; std::shared_ptr level_table; std::shared_ptr battle_params; @@ -43,10 +44,7 @@ struct ServerState { rw_lock lobbies_lock; std::map> id_to_lobby; - std::unordered_map> name_to_lobby; - - std::atomic next_lobby_id; - std::atomic next_game_id; + std::atomic next_lobby_id; std::set all_addresses; uint32_t local_address; @@ -62,12 +60,11 @@ struct ServerState { void send_lobby_join_notifications(std::shared_ptr l, std::shared_ptr joining_client); - std::shared_ptr find_lobby(int64_t lobby_id); - std::shared_ptr find_lobby(const std::u16string& name); + std::shared_ptr find_lobby(uint32_t lobby_id); std::vector> all_lobbies(); void add_lobby(std::shared_ptr l); - void remove_lobby(int64_t lobby_id); + void remove_lobby(uint32_t lobby_id); std::shared_ptr find_client(const char16_t* identifier = NULL, uint64_t serial_number = 0, std::shared_ptr l = NULL); diff --git a/system/config.json b/system/config.json index 412502ff..62b2e6db 100755 --- a/system/config.json +++ b/system/config.json @@ -17,7 +17,7 @@ // Number of worker threads to run "Threads": 1, // Set to false to disable the DNS server - "RunDNSServer": false, + "RunDNSServer": true, // **************** // INFORMATION MENU @@ -40,7 +40,7 @@ ["Event values", "$C7Display lobby event\nlist", "These values can be used with the $C6%sevent$C7 command.\n\nnone - no event\nxmas - Christmas event\nval - Valentine's Day\neaster - Easter Sunday event\nhallo - Halloween event\nsonic - Sonic Adventure DX event\nnewyear - New Year's event\nbval - White Day\nwedding - Wedding Day event\nspring - spring event\ns-spring - spring event with striped background\nsummer - summer event\ns-summer - summer event with striped background\nfall - fall event"], ["GC lobby types", "$C7Display lobby type\nlist for Episodes\nI & II", "These values can be used with the %sln command.\n$C6*$C7 indicates lobbies where players can't move.\n$C2Green$C7 indicates Episode 1 & 2 (GC) only lobbies.\n\nnormal - standard lobby\n$C2inormal$C7 - under standard lobby $C6*$C7\n$C2ipc$C7 - under PC lobby $C6*$C7\n$C2iball$C7 - under soccer lobby $C6*$C7\n$C2cave1$C7 - Cave 1 $C6*$C7\n$C2cave2u$C7 - Cave 2 Ultimate $C6*$C7\n$C2dragon$C7 - Dragon stage (floor is black)\n$C2derolle$C7 - De Rol Le stage (water/walls are gone)\n$C2volopt$C7 - Vol Opt stage\n$C2darkfalz$C7 - Dark Falz stage"], ["Ep3 lobby types", "$C7Display lobby type\nlist for Episode\nIII", "These values can be used with the %sln command.\n$C6*$C7 indicates lobbies where players can't move.\n$C8Pink$C7 indicates Episode 3 only lobbies.\n\nnormal - standard lobby\n$C8planet$C7 - Blank Ragol Lobby\n$C8clouds$C7 - Blank Sky Lobby\n$C8cave$C7 - Unguis Lapis\n$C8jungle$C7 - Episode 2 Jungle\n$C8forest2-1$C7 - Episode 1 Forest 2 (ground)\n$C8forest2-2$C7 - Episode 1 Forest 2 (near Dome)\n$C8windpower$C7\n$C8overview$C7\n$C8seaside$C7 - Episode 2 Seaside\n$C8some?$C7\n$C8dmorgue$C7 - Destroyed Morgue\n$C8caelum$C7 - Caelum\n$C8digital$C7\n$C8boss1$C7\n$C8boss2$C7\n$C8boss3$C7\n$C8knight$C7 - Leukon Knight stage\n$C8sky$C7 - Via Tubus\n$C8morgue$C7 - Morgue"], - ["Area list", "$C7Display stage code\nlist", "These values can be used with the $C6%swarp$C7 and\n$C6%smove$C7 commands.\n\n$C2Green$C7 areas will be empty unless you are in a quest.\n$C6Yellow$C7 areas will not allow you to move.\n\n $C8Episode 1 / Episode 2 / Episode 4$C7\n0: Pioneer 2 / Pioneer 2 / Pioneer 2\n1: Forest 1 / Temple Alpha / Crater East\n2: Forest 2 / Temple Beta / Crater West\n3: Caves 1 / Spaceship Alpha / Crater South\n4: Caves 2 / Spaceship Beta / Crater North\n5: Caves 3 / CCA / Crater Interior\n6: Mines 1 / Jungle North / Desert 1\n7: Mines 2 / Jungle South / Desert 2\n8: Ruins 1 / Mountain / Desert 3\n9: Ruins 2 / Seaside / Saint Million\n10: Ruins 3 / Seabed Upper / $C6Purgatory$C7\n11: Dragon / Seabed Lower\n12: De Rol Le / Gal Gryphon\n13: Vol Opt / Olga Flow\n14: Dark Falz / Barba Ray\n15: $C2Lobby$C7 / Gol Dragon\n16: $C6Battle 1$C7 / $C6Seaside Night$C7\n17: $C6Battle 2$C7 / $C2Tower$C7"], + ["Area list", "$C7Display stage code\nlist", "These values can be used with the $C6%swarp$C7 command.\n\n$C2Green$C7 areas will be empty unless you are in a quest.\n$C6Yellow$C7 areas will not allow you to move.\n\n $C8Episode 1 / Episode 2 / Episode 4$C7\n0: Pioneer 2 / Pioneer 2 / Pioneer 2\n1: Forest 1 / Temple Alpha / Crater East\n2: Forest 2 / Temple Beta / Crater West\n3: Caves 1 / Spaceship Alpha / Crater South\n4: Caves 2 / Spaceship Beta / Crater North\n5: Caves 3 / CCA / Crater Interior\n6: Mines 1 / Jungle North / Desert 1\n7: Mines 2 / Jungle South / Desert 2\n8: Ruins 1 / Mountain / Desert 3\n9: Ruins 2 / Seaside / Saint Million\n10: Ruins 3 / Seabed Upper / $C6Purgatory$C7\n11: Dragon / Seabed Lower\n12: De Rol Le / Gal Gryphon\n13: Vol Opt / Olga Flow\n14: Dark Falz / Barba Ray\n15: $C2Lobby$C7 / Gol Dragon\n16: $C6Battle 1$C7 / $C6Seaside Night$C7\n17: $C6Battle 2$C7 / $C2Tower$C7"], ], // ***************