diff --git a/src/Menu.hh b/src/Menu.hh index 7a0e7fcb..d5baad0f 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -20,6 +20,7 @@ constexpr uint32_t GAME = 0x44000044; constexpr uint32_t QUEST = 0x55000055; constexpr uint32_t QUEST_FILTER = 0x66000066; constexpr uint32_t PROXY_DESTINATIONS = 0x77000077; +constexpr uint32_t REDIRECT_DESTINATIONS = 0x78000078; constexpr uint32_t PROGRAMS = 0x88000088; constexpr uint32_t PATCHES = 0x99000099; constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA; @@ -33,6 +34,7 @@ constexpr uint32_t GO_TO_LOBBY = 0x11222211; constexpr uint32_t INFORMATION = 0x11333311; constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411; constexpr uint32_t PROXY_DESTINATIONS = 0x11555511; +constexpr uint32_t REDIRECT_DESTINATIONS = 0x11565611; constexpr uint32_t PATCHES = 0x11666611; constexpr uint32_t PROGRAMS = 0x11777711; constexpr uint32_t DISCONNECT = 0x11888811; @@ -48,6 +50,11 @@ constexpr uint32_t GO_BACK = 0x77FFFF77; constexpr uint32_t OPTIONS = 0x77EEEE77; } // namespace ProxyDestinationsMenuItemID +namespace RedirectDestinationsMenuItemID { +constexpr uint32_t GO_BACK = 0x78FFFF78; +constexpr uint32_t OPTIONS = 0x78EEEE78; +} // namespace RedirectDestinationsMenuItemID + namespace ProgramsMenuItemID { constexpr uint32_t GO_BACK = 0x88FFFF88; } diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index d8a7846a..e2285433 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -119,6 +119,11 @@ static void send_proxy_destinations_menu(shared_ptr c) { send_menu(c, s->proxy_destinations_menu_for_version(c->version())); } +static void send_redirect_destinations_menu(shared_ptr c) { + auto s = c->require_server_state(); + send_menu(c, s->redirect_destinations_menu_for_version(c->version())); +} + static bool send_enable_send_function_call_if_applicable(shared_ptr c) { auto s = c->require_server_state(); if (function_compiler_available() && @@ -206,6 +211,7 @@ static void send_main_menu(shared_ptr c) { 0); main_menu->items.emplace_back(MainMenuItemID::INFORMATION, u"Information", u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES); + uint32_t proxy_destinations_menu_item_flags = // DCNTE doesn't support multiple ship select menus without changing // servers (via a 19 command) apparently :( @@ -216,7 +222,24 @@ static void send_main_menu(shared_ptr c) { (s->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) | MenuItem::Flag::INVISIBLE_ON_BB; main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server", - u"Connect to another\nserver", proxy_destinations_menu_item_flags); + u"Connect to another\nserver through the\nproxy", proxy_destinations_menu_item_flags); + + // If the client is on a virtual connection, redirecting will not work + // properly (they'll just be reconencted to newserv), so hide the option + if (!c->channel.is_virtual_connection) { + uint32_t redirect_destinations_menu_item_flags = + // DCNTE doesn't support multiple ship select menus without changing + // servers (via a 19 command) apparently :( + MenuItem::Flag::INVISIBLE_ON_DCNTE | + (s->redirect_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) | + (s->redirect_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) | + (s->redirect_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) | + (s->redirect_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) | + MenuItem::Flag::INVISIBLE_ON_BB; + main_menu->items.emplace_back(MainMenuItemID::REDIRECT_DESTINATIONS, u"Other servers", + u"Connect to another\nserver directly", redirect_destinations_menu_item_flags); + } + main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests", u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); if (!s->is_replay) { @@ -1702,6 +1725,10 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) send_proxy_destinations_menu(c); break; + case MainMenuItemID::REDIRECT_DESTINATIONS: + send_redirect_destinations_menu(c); + break; + case MainMenuItemID::DOWNLOAD_QUESTS: { auto s = c->require_server_state(); if (c->flags & Client::Flag::IS_EPISODE_3) { @@ -1907,6 +1934,36 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) break; } + case MenuID::REDIRECT_DESTINATIONS: { + if (item_id == RedirectDestinationsMenuItemID::GO_BACK) { + send_main_menu(c); + + } else if (item_id == RedirectDestinationsMenuItemID::OPTIONS) { + send_menu(c, proxy_options_menu_for_client(c)); + + } else { + auto s = c->require_server_state(); + const pair* dest = nullptr; + try { + dest = &s->redirect_destinations_for_version(c->version()).at(item_id); + } catch (const out_of_range&) { + } + + if (!dest) { + send_message_box(c, u"$C6No such destination exists."); + c->should_disconnect = true; + } else { + // Clear Check Tactics menu so client won't see newserv tournament + // state while logically on another server + if ((c->flags & Client::Flag::IS_EPISODE_3) && !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) { + send_ep3_confirm_tournament_entry(c, nullptr); + } + send_reconnect(c, resolve_ipv4(dest->first), dest->second); + } + } + break; + } + case MenuID::GAME: { auto s = c->require_server_state(); auto game = s->find_lobby(item_id); diff --git a/src/ServerState.cc b/src/ServerState.cc index b292ca6e..e88ad0ae 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -353,6 +353,21 @@ shared_ptr ServerState::proxy_destinations_menu_for_version(GameVers } } +shared_ptr ServerState::redirect_destinations_menu_for_version(GameVersion version) const { + switch (version) { + case GameVersion::DC: + return this->redirect_destinations_menu_dc; + case GameVersion::PC: + return this->redirect_destinations_menu_pc; + case GameVersion::GC: + return this->redirect_destinations_menu_gc; + case GameVersion::XB: + return this->redirect_destinations_menu_xb; + default: + throw out_of_range("no redirect destinations menu exists for this version"); + } +} + const vector>& ServerState::proxy_destinations_for_version(GameVersion version) const { switch (version) { case GameVersion::DC: @@ -368,6 +383,21 @@ const vector>& ServerState::proxy_destinations_for_versio } } +const vector>& ServerState::redirect_destinations_for_version(GameVersion version) const { + switch (version) { + case GameVersion::DC: + return this->redirect_destinations_dc; + case GameVersion::PC: + return this->redirect_destinations_pc; + case GameVersion::GC: + return this->redirect_destinations_gc; + case GameVersion::XB: + return this->redirect_destinations_xb; + default: + throw out_of_range("no redirect destinations menu exists for this version"); + } +} + void ServerState::set_port_configuration( const vector& port_configs) { this->name_to_port_config.clear(); @@ -735,6 +765,36 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { this->information_menu_v3 = information_menu_v3; this->information_contents = information_contents; + auto generate_redirect_destinations_menu = [&](vector>& ret_pds, const char* key) -> shared_ptr { + shared_ptr ret(new Menu(MenuID::REDIRECT_DESTINATIONS, u"Other servers")); + ret_pds.clear(); + + try { + map sorted_jsons; + for (const auto& it : json.at(key).as_dict()) { + sorted_jsons.emplace(it.first, *it.second); + } + + ret->items.emplace_back(RedirectDestinationsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); + + uint32_t item_id = 0; + for (const auto& item : sorted_jsons) { + const string& netloc_str = item.second.as_string(); + const string& description = "$C7Remote server:\n$C6" + netloc_str; + ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0); + ret_pds.emplace_back(parse_netloc(netloc_str)); + item_id++; + } + } catch (const out_of_range&) { + } + return ret; + }; + + this->redirect_destinations_menu_dc = generate_redirect_destinations_menu(this->redirect_destinations_dc, "RedirectDestinations-DC"); + this->redirect_destinations_menu_pc = generate_redirect_destinations_menu(this->redirect_destinations_pc, "RedirectDestinations-PC"); + this->redirect_destinations_menu_gc = generate_redirect_destinations_menu(this->redirect_destinations_gc, "RedirectDestinations-GC"); + this->redirect_destinations_menu_xb = generate_redirect_destinations_menu(this->redirect_destinations_xb, "RedirectDestinations-XB"); + auto generate_proxy_destinations_menu = [&](vector>& ret_pds, const char* key) -> shared_ptr { shared_ptr ret(new Menu(MenuID::PROXY_DESTINATIONS, u"Proxy server")); ret_pds.clear(); @@ -763,14 +823,10 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { return ret; }; - this->proxy_destinations_menu_dc = generate_proxy_destinations_menu( - this->proxy_destinations_dc, "ProxyDestinations-DC"); - this->proxy_destinations_menu_pc = generate_proxy_destinations_menu( - this->proxy_destinations_pc, "ProxyDestinations-PC"); - this->proxy_destinations_menu_gc = generate_proxy_destinations_menu( - this->proxy_destinations_gc, "ProxyDestinations-GC"); - this->proxy_destinations_menu_xb = generate_proxy_destinations_menu( - this->proxy_destinations_xb, "ProxyDestinations-XB"); + this->proxy_destinations_menu_dc = generate_proxy_destinations_menu(this->proxy_destinations_dc, "ProxyDestinations-DC"); + this->proxy_destinations_menu_pc = generate_proxy_destinations_menu(this->proxy_destinations_pc, "ProxyDestinations-PC"); + this->proxy_destinations_menu_gc = generate_proxy_destinations_menu(this->proxy_destinations_gc, "ProxyDestinations-GC"); + this->proxy_destinations_menu_xb = generate_proxy_destinations_menu(this->proxy_destinations_xb, "ProxyDestinations-XB"); try { const string& netloc_str = json.get_string("ProxyDestination-Patch"); diff --git a/src/ServerState.hh b/src/ServerState.hh index 60663f11..ab5da938 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -124,6 +124,14 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr information_menu_v2; std::shared_ptr information_menu_v3; std::shared_ptr> information_contents; + std::shared_ptr redirect_destinations_menu_dc; + std::shared_ptr redirect_destinations_menu_pc; + std::shared_ptr redirect_destinations_menu_gc; + std::shared_ptr redirect_destinations_menu_xb; + std::vector> redirect_destinations_dc; + std::vector> redirect_destinations_pc; + std::vector> redirect_destinations_gc; + std::vector> redirect_destinations_xb; std::shared_ptr proxy_destinations_menu_dc; std::shared_ptr proxy_destinations_menu_pc; std::shared_ptr proxy_destinations_menu_gc; @@ -189,6 +197,8 @@ struct ServerState : public std::enable_shared_from_this { uint32_t connect_address_for_client(std::shared_ptr c) const; std::shared_ptr information_menu_for_version(GameVersion version) const; + std::shared_ptr redirect_destinations_menu_for_version(GameVersion version) const; + const std::vector>& redirect_destinations_for_version(GameVersion version) const; std::shared_ptr proxy_destinations_menu_for_version(GameVersion version) const; const std::vector>& proxy_destinations_for_version(GameVersion version) const; diff --git a/system/config.example.json b/system/config.example.json index c003c848..4812516d 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -136,6 +136,21 @@ // connect to 127.0.0.1:5059. "IPStackListen": [], + // Other servers to support redirecting to. If this is empty for any game + // version, the "Other servers" menu does not appear for that version. Entries + // in these dictionaries should be of the form "name": "address:port"; the + // names are used in the "Other servers" menu. Note that if you connect via + // the IP stack simulator on Dolphin, you cannot redirect your connection to + // another server and the "Other servers" menu will not appear for you (but + // you can still use the proxy server). + // Unlike ProxyDestinations (below), when a client chooses an item from the + // "Other servers" menu, they are redirected to connect directly to that + // server, and their connection no longer goes through newserv. + "RedirectDestinations-DC": {}, + "RedirectDestinations-PC": {}, + "RedirectDestinations-GC": {}, + "RedirectDestinations-XB": {}, + // Other servers to support proxying to. If this is empty for any game // version, the proxy server is disabled for that version. Entries in these // dictionaries should be of the form "name": "address:port"; the names are diff --git a/tests/config.json b/tests/config.json index 53d08ae4..76fe60d1 100644 --- a/tests/config.json +++ b/tests/config.json @@ -55,11 +55,11 @@ }, "ProxyDestinations-GC": { - "Schtserv": "149.56.167.128:9103", + "Schtserv": "psobb.dyndns.org:9103", "Sylverant": "sylverant.net:9103", }, "ProxyDestinations-PC": { - "Schtserv": "149.56.167.128:9100", + "Schtserv": "psobb.dyndns.org:9100", "Sylverant": "sylverant.net:9100", },