From e1638bdc67502fe553be5dc1e670515fee7b167a Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 8 Mar 2022 00:38:56 -0800 Subject: [PATCH] set up download quest menus --- src/IPStackSimulator.cc | 54 ++++++++------ src/Quest.cc | 28 +++----- src/Quest.hh | 11 +-- src/ReceiveCommands.cc | 152 ++++++++++++++++++++++------------------ src/SendCommands.hh | 21 +++--- src/ServerState.cc | 2 + 6 files changed, 145 insertions(+), 123 deletions(-) diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index f0bd2fd9..af854a08 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -498,11 +498,6 @@ void IPStackSimulator::on_client_tcp_frame( throw runtime_error("TCP SYN contains extra flags"); } - uint64_t key = this->tcp_conn_key_for_client_frame(fi); - if (c->tcp_connections.count(key)) { - throw runtime_error("TCP SYN received for already-open connection"); - } - StringReader options_r(fi.tcp + 1, fi.tcp_options_size); size_t max_frame_size = 1400; while (!options_r.eof()) { @@ -545,24 +540,41 @@ void IPStackSimulator::on_client_tcp_frame( } } - // Set up the IPStackSimulator end of the virtual connection - auto& conn = c->tcp_connections.emplace(key, IPClient::TCPConnection()).first->second; - conn.client = c; - conn.resend_push_event.reset(event_new(this->base.get(), -1, EV_TIMEOUT, - &IPStackSimulator::dispatch_on_resend_push, &conn)); - conn.server_addr = fi.ipv4->dest_addr; - conn.server_port = fi.tcp->dest_port; - conn.client_port = fi.tcp->src_port; - conn.next_client_seq = fi.tcp->seq_num + 1; - conn.acked_server_seq = random_object(); - conn.resend_push_usecs = DEFAULT_RESEND_PUSH_USECS; - conn.awaiting_first_ack = true; - conn.max_frame_size = max_frame_size; + uint64_t key = this->tcp_conn_key_for_client_frame(fi); + auto emplace_ret = c->tcp_connections.emplace(key, IPClient::TCPConnection()); + auto& conn = emplace_ret.first->second; string conn_str = this->state->ip_stack_debug ? this->str_for_tcp_connection(c, conn) : ""; - if (this->state->ip_stack_debug) { - log(INFO, "[IPStackSimulator] Client opened TCP connection %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")", - conn_str.c_str(), conn.acked_server_seq, conn.next_client_seq); + + if (emplace_ret.second) { + // Connection is new; initialize it + conn.client = c; + conn.resend_push_event.reset(event_new(this->base.get(), -1, EV_TIMEOUT, + &IPStackSimulator::dispatch_on_resend_push, &conn)); + conn.server_addr = fi.ipv4->dest_addr; + conn.server_port = fi.tcp->dest_port; + conn.client_port = fi.tcp->src_port; + conn.next_client_seq = fi.tcp->seq_num + 1; + conn.acked_server_seq = random_object(); + conn.resend_push_usecs = DEFAULT_RESEND_PUSH_USECS; + conn.awaiting_first_ack = true; + conn.max_frame_size = max_frame_size; + if (this->state->ip_stack_debug) { + log(INFO, "[IPStackSimulator] Client opened TCP connection %s (acked_server_seq=%08" PRIX32 ", next_client_seq=%08" PRIX32 ")", + conn_str.c_str(), conn.acked_server_seq, conn.next_client_seq); + } + + } else { + // Connection is NOT new; this is probably a resend of an earlier SYN + if (!conn.awaiting_first_ack) { + throw logic_error("SYN received on already-open connection after initial phase"); + } + // TODO: We should check the syn/ack numbers here instead of just assuming + // they're correct + if (this->state->ip_stack_debug) { + log(INFO, "[IPStackSimulator] Client resent SYN for TCP connection %s", + conn_str.c_str()); + } } // Send a SYN+ACK (send_tcp_frame always adds the ACK flag) diff --git a/src/Quest.cc b/src/Quest.cc index fa6c8ce6..82a1a7b9 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -442,7 +442,7 @@ shared_ptr QuestIndex::get_gba(const string& name) const { } vector> QuestIndex::filter(GameVersion version, - bool is_dcv1, QuestCategory category, uint8_t episode) const { + bool is_dcv1, QuestCategory category, int16_t episode) const { auto it = this->version_id_to_quest.lower_bound(make_pair(version, 0)); auto end_it = this->version_id_to_quest.upper_bound(make_pair(version, 0xFFFFFFFF)); @@ -453,9 +453,10 @@ vector> QuestIndex::filter(GameVersion version, continue; } - // only check episode and solo if the category isn't a mode (that is, ignore - // episode if querying for battle/challange/solo quests) - if (!category_is_mode(category) && ((q->episode != episode))) { + // Only check episode and solo if the category isn't a mode (that is, ignore + // episode if querying for battle/challenge/solo quests). Also, ignore + // ignore episode if it's < 0 (e.g. for the download quest menu). + if ((episode >= 0) && !category_is_mode(category) && ((q->episode != episode))) { continue; } @@ -485,7 +486,7 @@ static string create_download_quest_file(const string& compressed_data, return data; } -shared_ptr Quest::create_download_quest(const string& file_basename) const { +shared_ptr Quest::create_download_quest() const { if (this->category == QuestCategory::Download) { throw invalid_argument("quest is already a download quest"); } @@ -497,10 +498,10 @@ shared_ptr Quest::create_download_quest(const string& file_basename) cons reinterpret_cast(data_ptr)->is_download = 0x01; break; case GameVersion::PC: - reinterpret_cast(data_ptr)->is_download = 0x01; + reinterpret_cast(data_ptr)->is_download = 0x01; break; case GameVersion::GC: - reinterpret_cast(data_ptr)->is_download = 0x01; + reinterpret_cast(data_ptr)->is_download = 0x01; break; case GameVersion::BB: throw invalid_argument("PSOBB does not support download quests"); @@ -508,16 +509,8 @@ shared_ptr Quest::create_download_quest(const string& file_basename) cons throw invalid_argument("unknown game version"); } - shared_ptr dlq(new Quest(file_basename)); - dlq->quest_id = this->quest_id; + shared_ptr dlq(new Quest(*this)); dlq->category = QuestCategory::Download; - dlq->episode = this->episode; - dlq->is_dcv1 = this->is_dcv1; - dlq->joinable = this->joinable; - dlq->version = this->version; - dlq->name = this->name; - dlq->short_description = this->short_description; - dlq->long_description = this->long_description; dlq->bin_contents_ptr.reset(new string(create_download_quest_file( prs_compress(decompressed_bin), decompressed_bin.size()))); @@ -526,8 +519,5 @@ shared_ptr Quest::create_download_quest(const string& file_basename) cons dlq->dat_contents_ptr.reset(new string(create_download_quest_file( *dat_contents, prs_decompress_size(*dat_contents)))); - save_file(dlq->bin_filename(), *dlq->bin_contents_ptr); - save_file(dlq->dat_filename(), *dlq->dat_contents_ptr); - return dlq; } diff --git a/src/Quest.hh b/src/Quest.hh index 7b17fa2b..e79bb953 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -56,6 +56,10 @@ public: mutable std::shared_ptr dat_contents_ptr; Quest(const std::string& file_basename); + Quest(const Quest&) = default; + Quest(Quest&&) = default; + Quest& operator=(const Quest&) = default; + Quest& operator=(Quest&&) = default; std::string bin_filename() const; std::string dat_filename() const; @@ -63,8 +67,7 @@ public: std::shared_ptr bin_contents() const; std::shared_ptr dat_contents() const; - std::shared_ptr create_download_quest( - const std::string& file_basename) const; + std::shared_ptr create_download_quest() const; }; struct QuestIndex { @@ -82,7 +85,5 @@ struct QuestIndex { std::shared_ptr get(GameVersion version, uint32_t id) const; std::shared_ptr get_gba(const std::string& name) const; std::vector> filter(GameVersion version, - bool is_dcv1, QuestCategory category, uint8_t episode) const; + bool is_dcv1, QuestCategory category, int16_t episode) const; }; - -Quest create_download_quest(const Quest& src, size_t version); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 9f579013..c76fb6e6 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -38,6 +38,45 @@ enum ClientStateBB { +vector quest_categories_menu({ + 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(static_cast(QuestCategory::Battle), u"Battle", u"$E$C6Battle mode rule\nsets", 0), +}); + +vector quest_challenge_menu({ + MenuItem(static_cast(QuestCategory::Challenge), u"Challenge", u"$E$C6Challenge mode\nquests", 0), +}); + +vector quest_solo_menu({ + MenuItem(static_cast(QuestCategory::Solo), u"Solo Quests", u"$E$C6Quests that require\na single player", 0), +}); + +vector quest_government_menu({ + 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(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_connect(std::shared_ptr s, std::shared_ptr c) { @@ -636,6 +675,10 @@ void process_menu_selection(shared_ptr s, shared_ptr c, c->flags |= ClientFlag::InInformationMenu; break; + case MAIN_MENU_DOWNLOAD_QUESTS: + send_quest_menu(c, QUEST_FILTER_MENU_ID, quest_download_menu, true); + break; + case MAIN_MENU_DISCONNECT: c->should_disconnect = true; break; @@ -731,16 +774,19 @@ void process_menu_selection(shared_ptr s, shared_ptr c, send_lobby_message_box(c, u"$C6Quests are not available."); break; } - auto l = s->find_lobby(c->lobby_id); + shared_ptr l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr; auto quests = s->quest_index->filter(c->version, c->flags & ClientFlag::IsDCv1, - static_cast(cmd->item_id & 0xFF), l->episode - 1); + static_cast(cmd->item_id & 0xFF), + l.get() ? (l->episode - 1) : -1); if (quests.empty()) { send_lobby_message_box(c, u"$C6There are no quests\navailable in that\ncategory."); break; } - send_quest_menu(c, QUEST_MENU_ID, quests, false); + // Hack: assume the menu to be sent is the download quest menu if the + // client is not in any lobby + send_quest_menu(c, QUEST_MENU_ID, quests, !c->lobby_id); break; } @@ -755,10 +801,15 @@ void process_menu_selection(shared_ptr s, shared_ptr c, break; } - auto l = s->find_lobby(c->lobby_id); - if (!l->is_game()) { - send_lobby_message_box(c, u"$C6Quests cannot be loaded\nin lobbies."); - break; + // 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) { + auto l = s->find_lobby(c->lobby_id); + if (!l->is_game()) { + send_lobby_message_box(c, u"$C6Quests cannot be loaded\nin lobbies."); + break; + } } auto bin_basename = q->bin_filename(); @@ -766,25 +817,33 @@ void process_menu_selection(shared_ptr s, shared_ptr c, auto bin_contents = q->bin_contents(); auto dat_contents = q->dat_contents(); - if (q->joinable) { - l->flags |= LobbyFlag::JoinableQuestInProgress; - } else { - l->flags |= LobbyFlag::QuestInProgress; - } - l->loading_quest_id = q->quest_id; - for (size_t x = 0; x < l->max_clients; x++) { - if (!l->clients[x]) { - continue; + if (l) { + if (q->joinable) { + l->flags |= LobbyFlag::JoinableQuestInProgress; + } else { + l->flags |= LobbyFlag::QuestInProgress; + } + l->loading_quest_id = q->quest_id; + for (size_t x = 0; x < l->max_clients; x++) { + if (!l->clients[x]) { + continue; + } + + // TODO: It looks like blasting all the chunks to the client at once can + // cause GC clients to crash in rare cases. Find a way to slow this down + // (perhaps by only sending each new chunk when they acknowledge the + // previous chunk with a 44 [first chunk] or 13 [later chunks] command). + send_quest_file(l->clients[x], bin_basename, *bin_contents, false, false); + send_quest_file(l->clients[x], dat_basename, *dat_contents, false, false); + + l->clients[x]->flags |= ClientFlag::Loading; } - // TODO: It looks like blasting all the chunks to the client at once can - // cause GC clients to crash in rare cases. Find a way to slow this down - // (perhaps by only sending each new chunk when they acknowledge the - // previous chunk with a 44 [first chunk] or 13 [later chunks] command). - send_quest_file(l->clients[x], bin_basename, *bin_contents, false, false); - send_quest_file(l->clients[x], dat_basename, *dat_contents, false, false); - - l->clients[x]->flags |= ClientFlag::Loading; + } else { + // TODO: cache dlq somewhere maybe + auto dlq = q->create_download_quest(); + send_quest_file(c, bin_basename, *bin_contents, true, false); + send_quest_file(c, dat_basename, *dat_contents, true, false); } break; } @@ -851,43 +910,6 @@ void process_change_block(shared_ptr s, shared_ptr c, //////////////////////////////////////////////////////////////////////////////// // Quest commands -vector quest_categories_menu({ - 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(static_cast(QuestCategory::Battle), u"Battle", u"$E$C6Battle mode rule\nsets", 0), -}); - -vector quest_challenge_menu({ - MenuItem(static_cast(QuestCategory::Challenge), u"Challenge", u"$E$C6Challenge mode\nquests", 0), -}); - -vector quest_solo_menu({ - MenuItem(static_cast(QuestCategory::Solo), u"Solo Quests", u"$E$C6Quests that require\na single player", 0), -}); - -vector quest_government_menu({ - 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(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, uint16_t, uint32_t flag, uint16_t size, const void*) { // A2 check_size(size, 0); @@ -964,12 +986,6 @@ void process_gba_file_request(shared_ptr, shared_ptr c, send_quest_file(c, filename, *contents, false, false); } -void process_start_download_quest(shared_ptr, shared_ptr c, - uint16_t, uint32_t, uint16_t, const void*) { // A6 - // TODO implement this - send_text_message(c, u"$C6Download quests\nare not supported"); -} - //////////////////////////////////////////////////////////////////////////////// @@ -1994,7 +2010,7 @@ static process_command_t gc_handlers[0x100] = { // A0 process_change_ship, process_change_block, process_quest_list_request, nullptr, - nullptr, nullptr, process_start_download_quest, process_ignored_command, + nullptr, nullptr, process_ignored_command, process_ignored_command, nullptr, process_ignored_command, nullptr, nullptr, process_quest_ready, nullptr, nullptr, nullptr, diff --git a/src/SendCommands.hh b/src/SendCommands.hh index c66c641f..65d3e30b 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -16,17 +16,18 @@ -#define MAIN_MENU_ID 0x512CBD43 -#define INFORMATION_MENU_ID 0x93320CAA -#define LOBBY_MENU_ID 0x01F8471B -#define GAME_MENU_ID 0x205CD430 -#define QUEST_MENU_ID 0x7F02CA94 -#define QUEST_FILTER_MENU_ID 0xC38CA039 +#define MAIN_MENU_ID 0x60000000 +#define INFORMATION_MENU_ID 0x60000030 +#define LOBBY_MENU_ID 0x60000060 +#define GAME_MENU_ID 0x60000090 +#define QUEST_MENU_ID 0x600000C0 +#define QUEST_FILTER_MENU_ID 0x600000F0 -#define MAIN_MENU_GO_TO_LOBBY 0x00000001 -#define MAIN_MENU_INFORMATION 0x00000002 -#define MAIN_MENU_DISCONNECT 0x00000003 -#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF +#define MAIN_MENU_GO_TO_LOBBY 0x00000001 +#define MAIN_MENU_INFORMATION 0x00000002 +#define MAIN_MENU_DOWNLOAD_QUESTS 0x00000003 +#define MAIN_MENU_DISCONNECT 0x00000004 +#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF diff --git a/src/ServerState.cc b/src/ServerState.cc index 679ba299..eef8853d 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -25,6 +25,8 @@ ServerState::ServerState() u"Join the lobby.", 0); this->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information", u"View server information.", MenuItemFlag::RequiresMessageBoxes); + this->main_menu.emplace_back(MAIN_MENU_DOWNLOAD_QUESTS, u"Download quests", + u"Download quests.", 0); this->main_menu.emplace_back(MAIN_MENU_DISCONNECT, u"Disconnect", u"Disconnect.", 0);