From 0951132c01c4c82c6d6194af1022d0567dd55d31 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 29 Sep 2022 11:08:35 -0700 Subject: [PATCH] add proxy options menu --- README.md | 1 + src/ChatCommands.cc | 16 ++++++ src/Client.cc | 3 ++ src/Client.hh | 5 ++ src/CommandFormats.hh | 9 ++-- src/Main.cc | 3 +- src/Map.cc | 1 - src/Menu.hh | 19 +++++-- src/ProxyCommands.cc | 98 ++++++++++++++++++++++++------------ src/ProxyServer.cc | 1 + src/ProxyServer.hh | 1 + src/ReceiveCommands.cc | 109 +++++++++++++++++++++++++++++++++++++++-- src/SendCommands.cc | 5 -- src/ServerState.cc | 2 + src/ServerState.hh | 1 + src/StaticGameData.cc | 2 - 16 files changed, 222 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index d82fd179..d45f0f09 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Current known issues / missing features / things to do: - Implement private and overflow lobbies. - Enforce client-side size limits (e.g. for 60/62 commands) on the server side as well. (For 60/62 specifically, perhaps transform them to 6C/6D if needed.) - Encapsulate BB server-side random state and make replays deterministic. +- The internal menu abstraction is ugly and hard to work with. Rewrite it. ## Compatibility diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 4c5987b2..ede8c1fd 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -198,6 +198,21 @@ static void server_command_dbgid(shared_ptr, shared_ptr, c->prefer_high_lobby_client_id ? "high" : "low"); } +static void server_command_proxygc(shared_ptr, shared_ptr, + shared_ptr c, const std::u16string& args) { + 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); @@ -919,6 +934,7 @@ static const unordered_map chat_commands({ {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 "}}, diff --git a/src/Client.cc b/src/Client.cc index 1b8a2e86..abc40a8a 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -19,6 +19,7 @@ using namespace std; const uint64_t CLIENT_CONFIG_MAGIC = 0x492A890E82AC9839; +FileContentsCache client_options_cache(3600 * 1000 * 1000); static atomic next_id(1); @@ -59,6 +60,8 @@ Client::Client( switch_assist(false), can_chat(true), pending_bb_save_player_index(0), + proxy_save_files(false), + proxy_suppress_remote_login(false), dol_base_addr(0) { this->last_switch_enabled_command.subcommand = 0; memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr)); diff --git a/src/Client.hh b/src/Client.hh index 6979943f..fcea15b2 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -6,6 +6,7 @@ #include "Channel.hh" #include "CommandFormats.hh" +#include "FileContentsCache.hh" #include "FunctionCompiler.hh" #include "License.hh" #include "PatchFileIndex.hh" @@ -17,6 +18,7 @@ extern const uint64_t CLIENT_CONFIG_MAGIC; +extern FileContentsCache client_options_cache; @@ -112,6 +114,9 @@ struct Client { std::string pending_bb_save_username; uint8_t pending_bb_save_player_index; + bool proxy_save_files; + bool proxy_suppress_remote_login; + // DOL file loading state uint32_t dol_base_addr; std::shared_ptr loading_dol_file; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index e6d03a20..fd01b594 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -620,11 +620,10 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 { // 17 (S->C): Start encryption at login server (except on BB) // Same format and usage as 02 command, but a different copyright string: // "DreamCast Port Map. Copyright SEGA Enterprises. 1999" -// Unlike the 02 command, V3 clients will respond with a DB command the first -// time they receive a 17 command in any online session, with the exception of -// Episodes 1&2 trial edition (which responds with a 9A). After the first time, -// V3 clients will respond with a 9E. DCv1 will respond with a 90. Other non-V3 -// clients will respond with a 9A or 9D. +// Unlike the 02 command, V3 clients will respond with a DB command when they +// receive a 17 command in any online session, with the exception of Episodes +// 1&2 trial edition (which responds with a 9A). DCv1 will respond with a 90. +// Other non-V3 clients will respond with a 9A or 9D. // 18 (S->C): License verification result (PC/V3) // Behaves exactly the same as 9A (S->C). No arguments except header.flag. diff --git a/src/Main.cc b/src/Main.cc index b0afe25a..fb570545 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -621,9 +621,8 @@ int main(int argc, char** argv) { use_terminal_colors = true; } - shared_ptr state(new ServerState()); - shared_ptr base(event_base_new(), event_base_free); + shared_ptr state(new ServerState()); config_log.info("Reading network addresses"); state->all_addresses = get_local_addresses(); diff --git a/src/Map.cc b/src/Map.cc index 8f5e1b1a..c3240040 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -4,7 +4,6 @@ #include #include "Loggers.hh" -#include "FileContentsCache.hh" using namespace std; diff --git a/src/Menu.hh b/src/Menu.hh index 18054072..11d98825 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -22,6 +22,7 @@ namespace MenuID { constexpr uint32_t PROXY_DESTINATIONS = 0x77000077; constexpr uint32_t PROGRAMS = 0x88000088; constexpr uint32_t PATCHES = 0x99000099; + constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA; } namespace MainMenuItemID { @@ -37,19 +38,29 @@ namespace MainMenuItemID { namespace InformationMenuItemID { constexpr uint32_t GO_BACK = 0x22FFFF22; -}; +} namespace ProxyDestinationsMenuItemID { constexpr uint32_t GO_BACK = 0x77FFFF77; -}; + constexpr uint32_t OPTIONS = 0x77EEEE77; +} namespace ProgramsMenuItemID { constexpr uint32_t GO_BACK = 0x88FFFF88; -}; +} namespace PatchesMenuItemID { constexpr uint32_t GO_BACK = 0x99FFFF99; -}; +} + +namespace ProxyOptionsMenuItemID { + constexpr uint32_t GO_BACK = 0xAAFFFFAA; + constexpr uint32_t INFINITE_HP = 0xAA1111AA; + constexpr uint32_t INFINITE_TP = 0xAA2222AA; + constexpr uint32_t SWITCH_ASSIST = 0xAA3333AA; + constexpr uint32_t SAVE_FILES = 0xAA4444AA; + constexpr uint32_t SUPPRESS_LOGIN = 0xAA5555AA; +} diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 18f14618..2fea7648 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -62,29 +62,6 @@ static void check_implemented_subcommand( -static void send_text_message_to_client( - ProxyServer::LinkedSession& session, - uint8_t command, - const std::string& message) { - StringWriter w; - w.put({0, 0}); - if ((session.version == GameVersion::PC) || - (session.version == GameVersion::BB)) { - auto decoded = decode_sjis(message); - w.write(decoded.data(), decoded.size() * sizeof(decoded[0])); - w.put_u16l(0); - } else { - w.write(message); - w.put_u8(0); - } - while (w.size() & 3) { - w.put_u8(0); - } - session.client_channel.send(command, 0x00, w.str()); -} - - - // Command handlers. These are called to preprocess or react to specific // commands in either direction. If they return true, the command (which the // function may have modified) is forwarded to the other end; if they return @@ -125,9 +102,26 @@ static HandlerResult on_server_97(shared_ptr, return HandlerResult::Type::FORWARD; } +static HandlerResult on_client_gc_9E(shared_ptr, + ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { + if (session.suppress_remote_login) { + le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; + session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); + + S_UpdateClientConfig_DC_PC_V3_04 cmd = {0x00010000, session.license->serial_number, ClientConfig()}; + session.client_channel.send(0x04, 0x00, &cmd, sizeof(cmd)); + + return HandlerResult::Type::SUPPRESS; + + } else { + return HandlerResult::Type::FORWARD; + } +} + + static HandlerResult on_server_gc_9A(shared_ptr, ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (!session.license) { + if (!session.license || session.suppress_remote_login) { return HandlerResult::Type::FORWARD; } @@ -318,6 +312,39 @@ static HandlerResult on_server_dc_pc_v3_patch_02_17( session.server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; + } else if (session.suppress_remote_login) { + uint32_t guild_card_number; + if (session.remote_guild_card_number >= 0) { + guild_card_number = session.remote_guild_card_number; + log_info("Using Guild Card number %" PRIu32 " from session", guild_card_number); + } else { + guild_card_number = random_object(); + log_info("Using Guild Card number %" PRIu32 " from random generator", guild_card_number); + } + + uint32_t fake_serial_number = random_object() & 0x7FFFFFFF; + uint64_t fake_access_key = random_object(); + string fake_access_key_str = string_printf("00000000000%" PRIu64, fake_access_key); + if (fake_access_key_str.size() > 12) { + fake_access_key_str = fake_access_key_str.substr(fake_access_key_str.size() - 12); + } + + C_LoginExtended_GC_9E cmd; + cmd.player_tag = 0x00010000; + cmd.guild_card_number = guild_card_number; + cmd.unused = 0; + cmd.sub_version = session.sub_version; + cmd.is_extended = 0; + cmd.language = session.language; + cmd.serial_number = string_printf("%08" PRIX32, fake_serial_number); + cmd.access_key = fake_access_key_str; + cmd.serial_number2 = cmd.serial_number; + cmd.access_key2 = cmd.access_key; + cmd.name = session.character_name; + cmd.client_config.data = session.remote_client_config_data; + session.server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E)); + return HandlerResult::Type::SUPPRESS; + } else { // For command 02, send the same as if we had received 9A from the server return on_server_gc_9A(s, session, command, flag, data); @@ -389,6 +416,13 @@ static HandlerResult on_server_bb_03(shared_ptr s, static HandlerResult on_server_dc_pc_v3_04(shared_ptr, ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { + // Suppress extremely short commands from the server instead of disconnecting. + if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_V3_04, cfg)) { + le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; + session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); + return HandlerResult::Type::SUPPRESS; + } + // Some servers send a short 04 command if they don't use all of the 0x20 // bytes available. We should be prepared to handle that. auto& cmd = check_size_t(data, @@ -404,9 +438,10 @@ static HandlerResult on_server_dc_pc_v3_04(shared_ptr, session.remote_guild_card_number = cmd.guild_card_number; session.log.info("Remote guild card number set to %" PRId64, session.remote_guild_card_number); - send_text_message_to_client(session, 0x11, string_printf( + string message = string_printf( "The remote server\nhas assigned your\nGuild Card number:\n\tC6%" PRId64, - session.remote_guild_card_number)); + session.remote_guild_card_number); + send_ship_info(session.client_channel, decode_sjis(message)); } if (session.license) { cmd.guild_card_number = session.license->serial_number; @@ -431,10 +466,6 @@ static HandlerResult on_server_dc_pc_v3_04(shared_ptr, // the first 04 command the client has received. The client responds with a 96 // (checksum) in that case. if (!had_guild_card_number) { - // We don't actually have a client checksum, of course... hopefully just - // random data will do (probably no private servers check this at all) - // TODO: Presumably we can save these values from the client when they - // connected to newserv originally, but I'm too lazy to do this right now le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); } @@ -918,6 +949,7 @@ static HandlerResult on_server_65_67_68(shared_ptr, auto& cmd = check_size_t(data, expected_size, expected_size); bool modified = false; + size_t num_replacements = 0; session.lobby_client_id = cmd.client_id; update_leader_id(session, cmd.leader_id); for (size_t x = 0; x < flag; x++) { @@ -927,6 +959,7 @@ static HandlerResult on_server_65_67_68(shared_ptr, } else { if (session.license && (cmd.entries[x].lobby_data.guild_card == session.remote_guild_card_number)) { cmd.entries[x].lobby_data.guild_card = session.license->serial_number; + num_replacements++; modified = true; } session.lobby_players[index].guild_card_number = cmd.entries[x].lobby_data.guild_card; @@ -938,6 +971,9 @@ static HandlerResult on_server_65_67_68(shared_ptr, session.lobby_players[index].name.c_str()); } } + if (num_replacements > 1) { + throw runtime_error("proxied player appears multiple times in lobby"); + } if (session.override_lobby_event >= 0) { cmd.event = session.override_lobby_event; @@ -2157,7 +2193,7 @@ static on_command_t handlers[6][0x100][2] = { /* 9B */ {nullptr, nullptr}, /* 9C */ {nullptr, nullptr}, /* 9D */ {nullptr, nullptr}, - /* 9E */ {nullptr, nullptr}, + /* 9E */ {nullptr, on_client_gc_9E}, /* 9F */ {nullptr, nullptr}, // (GC) SERVER CLIENT /* A0 */ {nullptr, on_client_dc_pc_v3_A0_A1}, diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 59b4646a..cacef076 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -480,6 +480,7 @@ ProxyServer::LinkedSession::LinkedSession( infinite_hp(false), infinite_tp(false), save_files(false), + suppress_remote_login(false), function_call_return_value(-1), next_item_id(0x0F000000), override_section_id(-1), diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index c036eea5..2c003446 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -65,6 +65,7 @@ public: bool infinite_hp; bool infinite_tp; bool save_files; + bool suppress_remote_login; int64_t function_call_return_value; // -1 = don't block function calls G_SwitchStateChanged_6x05 last_switch_enabled_command; PlayerInventoryItem next_drop_item; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 17275c3a..b10bf865 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -875,18 +875,44 @@ static void on_menu_item_info_request(shared_ptr s, shared_ptrproxy_destinations_menu_for_version(c->version()); - // We use item_id + 1 here because "go back" is the first item - send_ship_info(c, menu.at(cmd.item_id + 1).description.c_str()); + // We use item_id + 2 here because "go back" and "options" are the + // first items + send_ship_info(c, menu.at(cmd.item_id + 2).description.c_str()); } catch (const out_of_range&) { send_ship_info(c, u"$C4Missing proxy\ndestination"); } } break; + case MenuID::PROXY_OPTIONS: + switch (cmd.item_id) { + case ProxyOptionsMenuItemID::GO_BACK: + send_ship_info(c, u"Return to the\nproxy menu"); + break; + case ProxyOptionsMenuItemID::INFINITE_HP: + send_ship_info(c, u"Enable or disable\ninfinite HP\n\nYou can change this\nduring the session\nwith the %sinfhp\ncommand"); + break; + case ProxyOptionsMenuItemID::INFINITE_TP: + send_ship_info(c, u"Enable or disable\ninfinite TP\n\nYou can change this\nduring the session\nwith the %sinftp\ncommand"); + break; + case ProxyOptionsMenuItemID::SWITCH_ASSIST: + send_ship_info(c, u"Enable or disable\nswitch assist\n\nYou can change this\nduring the session\nwith the %sswa\ncommand"); + break; + case ProxyOptionsMenuItemID::SAVE_FILES: + send_ship_info(c, u"Enable or disable\nsaving of files from\nthe remote server\n(quests, etc.)"); + break; + case ProxyOptionsMenuItemID::SUPPRESS_LOGIN: + send_ship_info(c, u"Enable or disable\nalternate login\nsequence"); + break; + } + break; + case MenuID::QUEST_FILTER: // Don't send anything here. The quest filter menu already has short // descriptions included with the entries, which the client shows in the @@ -1012,6 +1038,29 @@ static void on_menu_item_info_request(shared_ptr s, shared_ptr proxy_options_menu_for_client( + shared_ptr c) { + vector ret; + ret.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", + u"Return to the\nproxy menu", 0); + ret.emplace_back(ProxyOptionsMenuItemID::INFINITE_HP, + c->infinite_hp ? u"Infinite HP ON" : u"Infinite HP OFF", + u"Enable or disable\ninfinite HP", 0); + ret.emplace_back(ProxyOptionsMenuItemID::INFINITE_TP, + c->infinite_tp ? u"Infinite TP ON" : u"Infinite TP OFF", + u"Enable or disable\ninfinite TP", 0); + ret.emplace_back(ProxyOptionsMenuItemID::SWITCH_ASSIST, + c->switch_assist ? u"Switch assist ON" : u"Switch assist OFF", + u"Enable or disable\nswitch assist", 0); + ret.emplace_back(ProxyOptionsMenuItemID::SAVE_FILES, + c->proxy_save_files ? u"Save files ON" : u"Save files OFF", + u"Enable or disable\nsaving of files from\nthe remote server\n(quests, etc.)", 0); + ret.emplace_back(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, + c->proxy_suppress_remote_login ? u"Skip login ON" : u"Skip login OFF", + u"Enable or disable\nalternate login\nsequence", 0); + return ret; +} + static void on_menu_selection(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // 10 bool uses_unicode = ((c->version() == GameVersion::PC) || (c->version() == GameVersion::BB)); @@ -1066,6 +1115,13 @@ static void on_menu_selection(shared_ptr s, shared_ptr c, case MainMenuItemID::PROXY_DESTINATIONS: send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS, s->proxy_destinations_menu_for_version(c->version())); + try { + string key = string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number); + const auto& entry = client_options_cache.get_or_throw(key); + uint32_t proxy_remote_guild_card_number = stoul(entry->data, nullptr, 10); + string info_str = string_printf("Your remote Guild\nCard number is\noverridden as\n$C6%" PRIu32, proxy_remote_guild_card_number); + send_ship_info(c, decode_sjis(info_str)); + } catch (const out_of_range&) { } break; case MainMenuItemID::DOWNLOAD_QUESTS: @@ -1127,10 +1183,45 @@ static void on_menu_selection(shared_ptr s, shared_ptr c, break; } + case MenuID::PROXY_OPTIONS: { + switch (item_id) { + case ProxyOptionsMenuItemID::GO_BACK: + send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS, + s->proxy_destinations_menu_for_version(c->version())); + break; + case ProxyOptionsMenuItemID::INFINITE_HP: + c->infinite_hp = !c->infinite_hp; + goto resend_proxy_options_menu; + case ProxyOptionsMenuItemID::INFINITE_TP: + c->infinite_tp = !c->infinite_tp; + goto resend_proxy_options_menu; + case ProxyOptionsMenuItemID::SWITCH_ASSIST: + c->switch_assist = !c->switch_assist; + goto resend_proxy_options_menu; + case ProxyOptionsMenuItemID::SAVE_FILES: + c->proxy_save_files = !c->proxy_save_files; + goto resend_proxy_options_menu; + case ProxyOptionsMenuItemID::SUPPRESS_LOGIN: + c->proxy_suppress_remote_login = !c->proxy_suppress_remote_login; + resend_proxy_options_menu: + send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS, + proxy_options_menu_for_client(c)); + break; + default: + send_message_box(c, u"Incorrect menu item ID."); + break; + } + break; + } + case MenuID::PROXY_DESTINATIONS: { if (item_id == ProxyDestinationsMenuItemID::GO_BACK) { send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu); + } else if (item_id == ProxyDestinationsMenuItemID::OPTIONS) { + send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS, + proxy_options_menu_for_client(c)); + } else { const pair* dest = nullptr; try { @@ -1156,8 +1247,18 @@ static void on_menu_selection(shared_ptr s, shared_ptr c, send_update_client_config(c); s->proxy_server->delete_session(c->license->serial_number); - s->proxy_server->create_licensed_session( + auto session = s->proxy_server->create_licensed_session( c->license, local_port, c->version(), c->export_config_bb()); + session->infinite_hp = c->infinite_hp; + session->infinite_tp = c->infinite_tp; + session->switch_assist = c->switch_assist; + session->save_files = c->proxy_save_files; + session->suppress_remote_login = c->proxy_suppress_remote_login; + try { + string key = string_printf("proxy_remote_guild_card_number:%" PRIX32, c->license->serial_number); + const auto& entry = client_options_cache.get_or_throw(key); + session->remote_guild_card_number = stoul(entry->data, nullptr, 10); + } catch (const out_of_range&) { } send_reconnect(c, s->connect_address_for_client(c), local_port); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index b1c4c208..724caa29 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -21,11 +21,6 @@ using namespace std; -extern bool use_terminal_colors; -extern FileContentsCache file_cache; - - - const unordered_set v2_crypt_initial_client_commands({ 0x00260088, // (17) DCNTE license check 0x00280090, // (17) DCv1 license check diff --git a/src/ServerState.cc b/src/ServerState.cc index bf8f54f2..0e76f239 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -344,6 +344,8 @@ void ServerState::create_menus(shared_ptr config_json) { ret_menu.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); + ret_menu.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options", + u"Set proxy options", 0); uint32_t item_id = 0; for (const auto& item : items->as_dict()) { diff --git a/src/ServerState.hh b/src/ServerState.hh index c7c416ab..1b51cb44 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -100,6 +100,7 @@ struct ServerState { std::shared_ptr proxy_server; std::shared_ptr game_server; + std::shared_ptr client_options_cache; ServerState(); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index e73b4ce5..30ecc24c 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -2,8 +2,6 @@ #include -#include "FileContentsCache.hh" - using namespace std;