diff --git a/src/EventUtils.hh b/src/EventUtils.hh index ba0cc80c..a109e531 100644 --- a/src/EventUtils.hh +++ b/src/EventUtils.hh @@ -4,5 +4,31 @@ #include #include +#include +#include +#include void forward_to_event_thread(std::shared_ptr base, std::function&& fn); + +template +T call_on_event_thread(std::shared_ptr base, std::function&& compute) { + std::optional ret; + std::string exc_what; + std::mutex ret_lock; + std::condition_variable ret_cv; + std::unique_lock g(ret_lock); + forward_to_event_thread(base, [&]() -> void { + std::lock_guard g(ret_lock); + try { + ret = compute(); + } catch (const std::exception& e) { + exc_what = e.what(); + } + ret_cv.notify_one(); + }); + ret_cv.wait(g); + if (!ret.has_value()) { + throw std::runtime_error(exc_what); + } + return ret.value(); +} diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 4e69de53..bcf8d5c0 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -10,6 +10,7 @@ #include #include +#include "EventUtils.hh" #include "Loggers.hh" #include "ProxyServer.hh" #include "Server.hh" @@ -164,7 +165,9 @@ const string& HTTPServer::get_url_param( HTTPServer::HTTPServer(shared_ptr state) : state(state), - http(evhttp_new(this->state->base.get()), evhttp_free) { + base(event_base_new(), event_base_free), + http(evhttp_new(this->base.get()), evhttp_free), + th(&HTTPServer::thread_fn, this) { evhttp_set_gencb(this->http.get(), this->dispatch_handle_request, this); } @@ -193,11 +196,19 @@ void HTTPServer::add_socket(int fd) { evhttp_accept_socket(this->http.get(), fd); } +void HTTPServer::schedule_stop() { + event_base_loopexit(this->base.get(), nullptr); +} + +void HTTPServer::wait_for_stop() { + this->th.join(); +} + void HTTPServer::dispatch_handle_request(struct evhttp_request* req, void* ctx) { reinterpret_cast(ctx)->handle_request(req); } -JSON HTTPServer::generate_quest_json(shared_ptr q) const { +JSON HTTPServer::generate_quest_json_st(shared_ptr q) { if (!q) { return nullptr; } @@ -215,7 +226,7 @@ JSON HTTPServer::generate_quest_json(shared_ptr q) const { }); } -JSON HTTPServer::generate_client_config_json(const Client::Config& config) const { +JSON HTTPServer::generate_client_config_json_st(const Client::Config& config) { const char* drop_notifications_mode = "unknown"; switch (config.get_drop_notification_mode()) { case Client::ItemDropNotificationMode::NOTHING: @@ -255,7 +266,7 @@ JSON HTTPServer::generate_client_config_json(const Client::Config& config) const return ret; } -JSON HTTPServer::generate_license_json(shared_ptr l) const { +JSON HTTPServer::generate_license_json_st(shared_ptr l) { auto ret = JSON::dict({ {"SerialNumber", l->serial_number}, {"Flags", l->flags}, @@ -270,20 +281,20 @@ JSON HTTPServer::generate_license_json(shared_ptr l) const { return ret; }; -JSON HTTPServer::generate_game_client_json(shared_ptr c) const { +JSON HTTPServer::generate_game_client_json_st(shared_ptr c, shared_ptr item_name_index) { auto ret = JSON::dict({ {"ID", c->id}, {"RemoteAddress", render_sockaddr_storage(c->channel.remote_addr)}, {"Version", name_for_enum(c->version())}, {"SubVersion", c->sub_version}, - {"Config", this->generate_client_config_json(c->config)}, + {"Config", HTTPServer::generate_client_config_json_st(c->config)}, {"Language", name_for_language_code(c->language())}, {"LocationX", c->x}, {"LocationZ", c->z}, {"LocationFloor", c->floor}, {"CanChat", c->can_chat}, }); - ret.emplace("license", c->license ? this->generate_license_json(c->license) : JSON(nullptr)); + ret.emplace("license", c->license ? HTTPServer::generate_license_json_st(c->license) : JSON(nullptr)); auto l = c->lobby.lock(); if (l) { ret.emplace("LobbyID", l->lobby_id); @@ -311,14 +322,14 @@ JSON HTTPServer::generate_game_client_json(shared_ptr c) const { JSON items_json = JSON::list(); for (size_t z = 0; z < p->inventory.num_items; z++) { const auto& item = p->inventory.items[z]; - string description = this->state->describe_item(c->version(), item.data, false); - string data_str = item.data.hex(); auto item_dict = JSON::dict({ {"Flags", item.flags.load()}, - {"Data", std::move(data_str)}, - {"Description", std::move(description)}, + {"Data", item.data.hex()}, {"ItemID", item.data.id.load()}, }); + if (item_name_index) { + item_dict.emplace("Description", item_name_index->describe_item(item.data, false)); + } items_json.emplace_back(std::move(item_dict)); } ret.emplace("ATP", p->disp.stats.char_stats.atp.load()); @@ -419,7 +430,7 @@ JSON HTTPServer::generate_game_client_json(shared_ptr c) const { return ret; } -JSON HTTPServer::generate_proxy_client_json(shared_ptr ses) const { +JSON HTTPServer::generate_proxy_client_json_st(shared_ptr ses) { struct LobbyPlayer { uint32_t guild_card_number = 0; uint64_t xb_user_id = 0; @@ -459,7 +470,7 @@ JSON HTTPServer::generate_proxy_client_json(shared_ptrhardware_id}, {"RemoteGuildCardNumber", ses->remote_guild_card_number}, {"RemoteClientConfigData", format_data_string(&ses->remote_client_config_data[0], ses->remote_client_config_data.size())}, - {"Config", this->generate_client_config_json(ses->config)}, + {"Config", HTTPServer::generate_client_config_json_st(ses->config)}, {"Language", name_for_language_code(ses->language())}, {"LobbyClientID", ses->lobby_client_id}, {"LeaderClientID", ses->leader_client_id}, @@ -487,11 +498,11 @@ JSON HTTPServer::generate_proxy_client_json(shared_ptrlicense ? this->generate_license_json(ses->license) : JSON(nullptr)); + ret.emplace("License", ses->license ? HTTPServer::generate_license_json_st(ses->license) : JSON(nullptr)); return ret; } -JSON HTTPServer::generate_lobby_json(shared_ptr l) const { +JSON HTTPServer::generate_lobby_json_st(shared_ptr l, shared_ptr item_name_index) { std::array, 12> clients; auto client_ids_json = JSON::list(); @@ -569,23 +580,23 @@ JSON HTTPServer::generate_lobby_json(shared_ptr l) const { for (size_t floor = 0; floor < l->floor_item_managers.size(); floor++) { for (const auto& it : l->floor_item_managers[floor].items) { const auto& item = it.second; - string description = this->state->describe_item(l->base_version, item->data, false); - string data_str = item->data.hex(); auto item_dict = JSON::dict({ {"LocationFloor", floor}, {"LocationX", item->x}, {"LocationZ", item->z}, {"DropNumber", item->drop_number}, {"VisibilityFlags", item->visibility_flags}, - {"Data", std::move(data_str)}, - {"Description", std::move(description)}, + {"Data", item->data.hex()}, {"ItemID", item->data.id.load()}, }); + if (item_name_index) { + item_dict.emplace("Description", item_name_index->describe_item(item->data, false)); + } floor_items_json.emplace_back(std::move(item_dict)); } } ret.emplace("FloorItems", std::move(floor_items_json)); - ret.emplace("Quest", this->generate_quest_json(l->quest)); + ret.emplace("Quest", HTTPServer::generate_quest_json_st(l->quest)); } else { ret.emplace("BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)); @@ -691,119 +702,130 @@ JSON HTTPServer::generate_lobby_json(shared_ptr l) const { } JSON HTTPServer::generate_game_server_clients_json() const { - JSON res = JSON::list(); - for (const auto& it : this->state->channel_to_client) { - res.emplace_back(this->generate_game_client_json(it.second)); - } - return res; + return call_on_event_thread(this->state->base, [&]() { + auto res = JSON::list(); + for (const auto& it : this->state->channel_to_client) { + res.emplace_back(this->generate_game_client_json_st(it.second, this->state->item_name_index_opt(it.second->version()))); + } + return res; + }); } JSON HTTPServer::generate_proxy_server_clients_json() const { - JSON res = JSON::list(); - for (const auto& it : this->state->proxy_server->all_sessions()) { - res.emplace_back(this->generate_proxy_client_json(it.second)); - } - return res; + return call_on_event_thread(this->state->base, [&]() { + JSON res = JSON::list(); + for (const auto& it : this->state->proxy_server->all_sessions()) { + res.emplace_back(this->generate_proxy_client_json_st(it.second)); + } + return res; + }); } JSON HTTPServer::generate_server_info_json() const { - size_t game_count = 0; - size_t lobby_count = 0; - for (const auto& it : this->state->id_to_lobby) { - if (it.second->is_game()) { - game_count++; - } else { - lobby_count++; + return call_on_event_thread(this->state->base, [&]() { + size_t game_count = 0; + size_t lobby_count = 0; + for (const auto& it : this->state->id_to_lobby) { + if (it.second->is_game()) { + game_count++; + } else { + lobby_count++; + } } - } - uint64_t uptime_usecs = now() - this->state->creation_time; - return JSON::dict({ - {"StartTimeUsecs", this->state->creation_time}, - {"StartTime", format_time(this->state->creation_time)}, - {"UptimeUsecs", uptime_usecs}, - {"Uptime", format_duration(uptime_usecs)}, - {"LobbyCount", lobby_count}, - {"GameCount", game_count}, - {"ClientCount", this->state->channel_to_client.size()}, - {"ProxySessionCount", this->state->proxy_server->num_sessions()}, - {"ServerName", this->state->name}, + uint64_t uptime_usecs = now() - this->state->creation_time; + return JSON::dict({ + {"StartTimeUsecs", this->state->creation_time}, + {"StartTime", format_time(this->state->creation_time)}, + {"UptimeUsecs", uptime_usecs}, + {"Uptime", format_duration(uptime_usecs)}, + {"LobbyCount", lobby_count}, + {"GameCount", game_count}, + {"ClientCount", this->state->channel_to_client.size()}, + {"ProxySessionCount", this->state->proxy_server->num_sessions()}, + {"ServerName", this->state->name}, + }); }); } JSON HTTPServer::generate_lobbies_json() const { - JSON res = JSON::list(); - for (const auto& it : this->state->id_to_lobby) { - res.emplace_back(this->generate_lobby_json(it.second)); - } - return res; + return call_on_event_thread(this->state->base, [&]() { + JSON res = JSON::list(); + for (const auto& it : this->state->id_to_lobby) { + res.emplace_back(this->generate_lobby_json_st(it.second, this->state->item_name_index_opt(it.second->base_version))); + } + return res; + }); } JSON HTTPServer::generate_summary_json() const { - auto clients_json = JSON::list(); - for (const auto& it : this->state->channel_to_client) { - auto c = it.second; - auto p = c->character(false, false); - auto l = c->lobby.lock(); - clients_json.emplace_back(JSON::dict({ - {"ID", c->id}, - {"SerialNumber", c->license ? c->license->serial_number : JSON(nullptr)}, - {"Name", p ? p->disp.name.decode(it.second->language()) : JSON(nullptr)}, - {"Version", name_for_enum(it.second->version())}, - {"Language", name_for_language_code(it.second->language())}, - {"Level", p ? p->disp.stats.level + 1 : JSON(nullptr)}, - {"Class", p ? name_for_char_class(p->disp.visual.char_class) : JSON(nullptr)}, - {"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : JSON(nullptr)}, - {"LobbyID", l ? l->lobby_id : JSON(nullptr)}, - })); - } - - auto proxy_clients_json = JSON::list(); - for (const auto& it : this->state->proxy_server->all_sessions()) { - proxy_clients_json.emplace_back(JSON::dict({ - {"SerialNumber", it.second->license ? it.second->license->serial_number : JSON(nullptr)}, - {"Name", it.second->character_name}, - {"Version", name_for_enum(it.second->version())}, - {"Language", name_for_language_code(it.second->language())}, - })); - } - - auto games_json = JSON::list(); - for (const auto& it : this->state->id_to_lobby) { - auto l = it.second; - if (l->is_game()) { - auto game_json = JSON::dict({ - {"ID", l->lobby_id}, - {"Name", l->name}, - {"BaseVersion", name_for_enum(l->base_version)}, - {"Players", l->count_clients()}, - {"CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED)}, - {"Episode", name_for_episode(l->episode)}, - {"HasPassword", !l->password.empty()}, - }); - if (l->episode == Episode::EP3) { - auto ep3s = l->ep3_server; - game_json.emplace("BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)); - game_json.emplace("IsSpectatorTeam", l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)); - game_json.emplace("MapNumber", (ep3s && ep3s->last_chosen_map) ? ep3s->last_chosen_map->map_number : JSON(nullptr)); - game_json.emplace("Rules", (ep3s && ep3s->map_and_rules) ? ep3s->map_and_rules->rules.json() : nullptr); - } else { - game_json.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)); - game_json.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)); - game_json.emplace("SectionID", name_for_section_id(l->section_id)); - game_json.emplace("Mode", name_for_mode(l->mode)); - game_json.emplace("Difficulty", name_for_difficulty(l->difficulty)); - game_json.emplace("Quest", this->generate_quest_json(l->quest)); - } - games_json.emplace_back(std::move(game_json)); + auto ret = call_on_event_thread(this->state->base, [&]() { + auto clients_json = JSON::list(); + for (const auto& it : this->state->channel_to_client) { + auto c = it.second; + auto p = c->character(false, false); + auto l = c->lobby.lock(); + clients_json.emplace_back(JSON::dict({ + {"ID", c->id}, + {"SerialNumber", c->license ? c->license->serial_number : JSON(nullptr)}, + {"Name", p ? p->disp.name.decode(it.second->language()) : JSON(nullptr)}, + {"Version", name_for_enum(it.second->version())}, + {"Language", name_for_language_code(it.second->language())}, + {"Level", p ? p->disp.stats.level + 1 : JSON(nullptr)}, + {"Class", p ? name_for_char_class(p->disp.visual.char_class) : JSON(nullptr)}, + {"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : JSON(nullptr)}, + {"LobbyID", l ? l->lobby_id : JSON(nullptr)}, + })); } - } - return JSON::dict({ - {"Clients", std::move(clients_json)}, - {"ProxyClients", std::move(proxy_clients_json)}, - {"Games", std::move(games_json)}, - {"Server", this->generate_server_info_json()}, + auto proxy_clients_json = JSON::list(); + for (const auto& it : this->state->proxy_server->all_sessions()) { + proxy_clients_json.emplace_back(JSON::dict({ + {"SerialNumber", it.second->license ? it.second->license->serial_number : JSON(nullptr)}, + {"Name", it.second->character_name}, + {"Version", name_for_enum(it.second->version())}, + {"Language", name_for_language_code(it.second->language())}, + })); + } + + auto games_json = JSON::list(); + for (const auto& it : this->state->id_to_lobby) { + auto l = it.second; + if (l->is_game()) { + auto game_json = JSON::dict({ + {"ID", l->lobby_id}, + {"Name", l->name}, + {"BaseVersion", name_for_enum(l->base_version)}, + {"Players", l->count_clients()}, + {"CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED)}, + {"Episode", name_for_episode(l->episode)}, + {"HasPassword", !l->password.empty()}, + }); + if (l->episode == Episode::EP3) { + auto ep3s = l->ep3_server; + game_json.emplace("BattleInProgress", l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)); + game_json.emplace("IsSpectatorTeam", l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)); + game_json.emplace("MapNumber", (ep3s && ep3s->last_chosen_map) ? ep3s->last_chosen_map->map_number : JSON(nullptr)); + game_json.emplace("Rules", (ep3s && ep3s->map_and_rules) ? ep3s->map_and_rules->rules.json() : nullptr); + } else { + game_json.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)); + game_json.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)); + game_json.emplace("SectionID", name_for_section_id(l->section_id)); + game_json.emplace("Mode", name_for_mode(l->mode)); + game_json.emplace("Difficulty", name_for_difficulty(l->difficulty)); + game_json.emplace("Quest", this->generate_quest_json_st(l->quest)); + } + games_json.emplace_back(std::move(game_json)); + } + } + + return JSON::dict({ + {"Clients", std::move(clients_json)}, + {"ProxyClients", std::move(proxy_clients_json)}, + {"Games", std::move(games_json)}, + }); }); + ret.emplace("Server", this->generate_server_info_json()); + return ret; } JSON HTTPServer::generate_all_json() const { @@ -816,13 +838,18 @@ JSON HTTPServer::generate_all_json() const { } JSON HTTPServer::generate_ep3_cards_json(bool trial) const { - const auto& index = trial ? this->state->ep3_card_index_trial : this->state->ep3_card_index; + auto index = call_on_event_thread>(this->state->base, [&]() { + return trial ? this->state->ep3_card_index_trial : this->state->ep3_card_index; + }); return index->definitions_json(); } JSON HTTPServer::generate_rare_tables_json() const { + auto sets = call_on_event_thread>>(this->state->base, [&]() { + return this->state->rare_item_sets; + }); JSON ret = JSON::list(); - for (const auto& it : this->state->rare_item_sets) { + for (const auto& it : sets) { ret.emplace_back(it.first); } return ret; @@ -830,25 +857,28 @@ JSON HTTPServer::generate_rare_tables_json() const { JSON HTTPServer::generate_rare_table_json(const std::string& table_name) const { try { - const auto& table = this->state->rare_item_sets.at(table_name); - shared_ptr name_index; - if (ends_with(table_name, "-v1")) { - name_index = this->state->item_name_index(Version::DC_V1); - } else if (ends_with(table_name, "-v2")) { - name_index = this->state->item_name_index(Version::PC_V2); - } else if (ends_with(table_name, "-v3")) { - name_index = this->state->item_name_index(Version::GC_V3); - } else if (ends_with(table_name, "-v4")) { - name_index = this->state->item_name_index(Version::BB_V4); - } - return table->json(name_index); + auto colls = call_on_event_thread, shared_ptr>>(this->state->base, [&]() { + const auto& table = this->state->rare_item_sets.at(table_name); + shared_ptr name_index; + if (ends_with(table_name, "-v1")) { + name_index = this->state->item_name_index_opt(Version::DC_V1); + } else if (ends_with(table_name, "-v2")) { + name_index = this->state->item_name_index_opt(Version::PC_V2); + } else if (ends_with(table_name, "-v3")) { + name_index = this->state->item_name_index_opt(Version::GC_V3); + } else if (ends_with(table_name, "-v4")) { + name_index = this->state->item_name_index_opt(Version::BB_V4); + } + return make_pair(table, name_index); + }); + return colls.first->json(colls.second); } catch (const out_of_range&) { throw http_error(404, "table does not exist"); } } void HTTPServer::handle_request(struct evhttp_request* req) { - JSON ret; + shared_ptr ret; uint32_t serialize_options = 0; try { string uri = evhttp_request_get_uri(req); @@ -862,7 +892,10 @@ void HTTPServer::handle_request(struct evhttp_request* req) { static const string default_format_option = "false"; if (this->get_url_param(query, "format", &default_format_option) == "true") { - serialize_options = JSON::SerializeOption::FORMAT | JSON::SerializeOption::SORT_DICT_KEYS; + serialize_options |= JSON::SerializeOption::FORMAT | JSON::SerializeOption::SORT_DICT_KEYS; + } + if (this->get_url_param(query, "hex", &default_format_option) == "true") { + serialize_options |= JSON::SerializeOption::HEX_INTEGERS; } if (uri == "/") { @@ -870,6 +903,7 @@ void HTTPServer::handle_request(struct evhttp_request* req) { "/y/data/ep3-cards", "/y/data/ep3-cards-trial", "/y/data/rare-tables", + "/y/data/rare-tables/", "/y/data/config", "/y/clients", "/y/proxy-clients", @@ -878,30 +912,30 @@ void HTTPServer::handle_request(struct evhttp_request* req) { "/y/summary", "/y/all", }); - ret = JSON::dict({{"endpoints", std::move(endpoints_json)}}); + ret = make_shared(JSON::dict({{"endpoints", std::move(endpoints_json)}})); } else if (uri == "/y/data/ep3-cards") { - ret = this->generate_ep3_cards_json(false); + ret = make_shared(this->generate_ep3_cards_json(false)); } else if (uri == "/y/data/ep3-cards-trial") { - ret = this->generate_ep3_cards_json(true); + ret = make_shared(this->generate_ep3_cards_json(true)); } else if (uri == "/y/data/rare-tables") { - ret = this->generate_rare_tables_json(); + ret = make_shared(this->generate_rare_tables_json()); } else if (!strncmp(uri.c_str(), "/y/data/rare-tables/", 20)) { - ret = this->generate_rare_table_json(uri.substr(20)); + ret = make_shared(this->generate_rare_table_json(uri.substr(20))); } else if (uri == "/y/data/config") { - ret = this->state->config_json; + ret = call_on_event_thread>(this->state->base, [this]() { return this->state->config_json; }); } else if (uri == "/y/clients") { - ret = this->generate_game_server_clients_json(); + ret = make_shared(this->generate_game_server_clients_json()); } else if (uri == "/y/proxy-clients") { - ret = this->generate_proxy_server_clients_json(); + ret = make_shared(this->generate_proxy_server_clients_json()); } else if (uri == "/y/lobbies") { - ret = this->generate_lobbies_json(); + ret = make_shared(this->generate_lobbies_json()); } else if (uri == "/y/server") { - ret = this->generate_server_info_json(); + ret = make_shared(this->generate_server_info_json()); } else if (uri == "/y/summary") { - ret = this->generate_summary_json(); + ret = make_shared(this->generate_summary_json()); } else if (uri == "/y/all") { - ret = this->generate_all_json(); + ret = make_shared(this->generate_all_json()); } else { throw http_error(404, "unknown action"); @@ -922,10 +956,14 @@ void HTTPServer::handle_request(struct evhttp_request* req) { } unique_ptr out_buffer(evbuffer_new(), evbuffer_free); - string* serialized = new string(ret.serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options)); + string* serialized = new string(ret->serialize(JSON::SerializeOption::ESCAPE_CONTROLS_ONLY | serialize_options)); auto cleanup = +[](const void*, size_t, void* s) -> void { delete reinterpret_cast(s); }; evbuffer_add_reference(out_buffer.get(), serialized->data(), serialized->size(), cleanup, serialized); this->send_response(req, 200, "application/json", out_buffer.get()); } + +void HTTPServer::thread_fn() { + event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY); +} diff --git a/src/HTTPServer.hh b/src/HTTPServer.hh index 72de625a..69845f74 100644 --- a/src/HTTPServer.hh +++ b/src/HTTPServer.hh @@ -25,6 +25,9 @@ public: void listen(int port); void add_socket(int fd); + void schedule_stop(); + void wait_for_stop(); + protected: class http_error : public std::runtime_error { public: @@ -33,7 +36,11 @@ protected: }; std::shared_ptr state; + std::shared_ptr base; std::shared_ptr http; + std::thread th; + + void thread_fn(); static void dispatch_handle_request(struct evhttp_request* req, void* ctx); void handle_request(struct evhttp_request* req); @@ -49,12 +56,12 @@ protected: const std::string& key, const std::string* _default = nullptr); - JSON generate_quest_json(std::shared_ptr q) const; - JSON generate_client_config_json(const Client::Config& config) const; - JSON generate_license_json(std::shared_ptr l) const; - JSON generate_game_client_json(std::shared_ptr c) const; - JSON generate_proxy_client_json(std::shared_ptr ses) const; - JSON generate_lobby_json(std::shared_ptr l) const; + static JSON generate_quest_json_st(std::shared_ptr q); + static JSON generate_client_config_json_st(const Client::Config& config); + static JSON generate_license_json_st(std::shared_ptr l); + static JSON generate_game_client_json_st(std::shared_ptr c, std::shared_ptr item_name_index); + static JSON generate_proxy_client_json_st(std::shared_ptr ses); + static JSON generate_lobby_json_st(std::shared_ptr l, std::shared_ptr item_name_index); JSON generate_game_server_clients_json() const; JSON generate_proxy_server_clients_json() const; JSON generate_server_info_json() const; diff --git a/src/Main.cc b/src/Main.cc index 8c928d48..22c9650e 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2518,6 +2518,9 @@ Action a_run_server_replay_log( if (state->bb_patch_server) { state->bb_patch_server->schedule_stop(); } + if (http_server) { + http_server->schedule_stop(); + } if (state->pc_patch_server) { config_log.info("Waiting for PC_V2 patch server to stop"); state->pc_patch_server->wait_for_stop(); @@ -2526,6 +2529,10 @@ Action a_run_server_replay_log( config_log.info("Waiting for BB_V4 patch server to stop"); state->bb_patch_server->wait_for_stop(); } + if (http_server) { + config_log.info("Waiting for HTTP server to stop"); + http_server->wait_for_stop(); + } state->proxy_server.reset(); // Break reference cycle }); diff --git a/src/ServerState.cc b/src/ServerState.cc index 3e4d2529..98e5b016 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -393,8 +393,12 @@ shared_ptr ServerState::item_stack_limits(Version v return ret; } +shared_ptr ServerState::item_name_index_opt(Version version) const { + return this->item_name_indexes.at(static_cast(version)); +} + shared_ptr ServerState::item_name_index(Version version) const { - auto ret = this->item_name_indexes.at(static_cast(version)); + auto ret = this->item_name_index_opt(version); if (ret == nullptr) { throw runtime_error("no item name index exists for this version"); } @@ -559,11 +563,11 @@ void ServerState::load_config_early() { } config_log.info("Loading configuration"); - this->config_json = JSON::parse(load_file(this->config_filename)); + this->config_json = make_shared(JSON::parse(load_file(this->config_filename))); auto parse_behavior_switch = [&](const string& json_key, BehaviorSwitch default_value) -> ServerState::BehaviorSwitch { try { - string behavior = this->config_json.get_string(json_key); + string behavior = this->config_json->get_string(json_key); if (behavior == "Off") { return ServerState::BehaviorSwitch::OFF; } else if (behavior == "OffByDefault") { @@ -580,11 +584,11 @@ void ServerState::load_config_early() { } }; - this->name = this->config_json.at("ServerName").as_string(); + this->name = this->config_json->at("ServerName").as_string(); if (!this->one_time_config_loaded) { try { - this->username = this->config_json.at("User").as_string(); + this->username = this->config_json->at("User").as_string(); if (this->username == "$SUDO_USER") { const char* user_from_env = getenv("SUDO_USER"); if (!user_from_env) { @@ -595,15 +599,15 @@ void ServerState::load_config_early() { } catch (const out_of_range&) { } - this->set_port_configuration(parse_port_configuration(this->config_json.at("PortConfiguration"))); + this->set_port_configuration(parse_port_configuration(this->config_json->at("PortConfiguration"))); try { - auto spec = this->parse_port_spec(this->config_json.at("DNSServerPort")); + auto spec = this->parse_port_spec(this->config_json->at("DNSServerPort")); this->dns_server_addr = std::move(spec.first); this->dns_server_port = spec.second; } catch (const out_of_range&) { } try { - for (const auto& item : this->config_json.at("IPStackListen").as_list()) { + for (const auto& item : this->config_json->at("IPStackListen").as_list()) { if (item->is_int()) { this->ip_stack_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int())); } else { @@ -613,7 +617,7 @@ void ServerState::load_config_early() { } catch (const out_of_range&) { } try { - for (const auto& item : this->config_json.at("PPPStackListen").as_list()) { + for (const auto& item : this->config_json->at("PPPStackListen").as_list()) { if (item->is_int()) { this->ppp_stack_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int())); } else { @@ -623,7 +627,7 @@ void ServerState::load_config_early() { } catch (const out_of_range&) { } try { - for (const auto& item : this->config_json.at("PPPRawListen").as_list()) { + for (const auto& item : this->config_json->at("PPPRawListen").as_list()) { if (item->is_int()) { this->ppp_raw_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int())); } else { @@ -633,7 +637,7 @@ void ServerState::load_config_early() { } catch (const out_of_range&) { } try { - for (const auto& item : this->config_json.at("HTTPListen").as_list()) { + for (const auto& item : this->config_json->at("HTTPListen").as_list()) { if (item->is_int()) { this->http_addresses.emplace_back(string_printf("0.0.0.0:%" PRId64, item->as_int())); } else { @@ -646,7 +650,7 @@ void ServerState::load_config_early() { this->one_time_config_loaded = true; } - auto local_address_str = this->config_json.at("LocalAddress").as_string(); + auto local_address_str = this->config_json->at("LocalAddress").as_string(); try { this->local_address = this->all_addresses.at(local_address_str); string addr_str = string_for_address(this->local_address); @@ -659,7 +663,7 @@ void ServerState::load_config_early() { this->all_addresses.erase(""); this->all_addresses.emplace("", this->local_address); - auto external_address_str = this->config_json.at("ExternalAddress").as_string(); + auto external_address_str = this->config_json->at("ExternalAddress").as_string(); try { this->external_address = this->all_addresses.at(external_address_str); string addr_str = string_for_address(this->external_address); @@ -672,32 +676,32 @@ void ServerState::load_config_early() { this->all_addresses.erase(""); this->all_addresses.emplace("", this->external_address); - this->client_ping_interval_usecs = this->config_json.get_int("ClientPingInterval", 30000000); - this->client_idle_timeout_usecs = this->config_json.get_int("ClientIdleTimeout", 60000000); - this->patch_client_idle_timeout_usecs = this->config_json.get_int("PatchClientIdleTimeout", 300000000); + this->client_ping_interval_usecs = this->config_json->get_int("ClientPingInterval", 30000000); + this->client_idle_timeout_usecs = this->config_json->get_int("ClientIdleTimeout", 60000000); + this->patch_client_idle_timeout_usecs = this->config_json->get_int("PatchClientIdleTimeout", 300000000); - this->ip_stack_debug = this->config_json.get_bool("IPStackDebug", false); - this->allow_unregistered_users = this->config_json.get_bool("AllowUnregisteredUsers", false); - this->allow_pc_nte = this->config_json.get_bool("AllowPCNTE", false); - this->use_temp_licenses_for_prototypes = this->config_json.get_bool("UseTemporaryLicensesForPrototypes", true); - this->allowed_drop_modes_v1_v2_normal = this->config_json.get_int("AllowedDropModesV1V2Normal", 0x1F); - this->allowed_drop_modes_v1_v2_battle = this->config_json.get_int("AllowedDropModesV1V2Battle", 0x07); - this->allowed_drop_modes_v1_v2_challenge = this->config_json.get_int("AllowedDropModesV1V2Challenge", 0x07); - this->allowed_drop_modes_v3_normal = this->config_json.get_int("AllowedDropModesV3Normal", 0x1F); - this->allowed_drop_modes_v3_battle = this->config_json.get_int("AllowedDropModesV3Battle", 0x07); - this->allowed_drop_modes_v3_challenge = this->config_json.get_int("AllowedDropModesV3Challenge", 0x07); - this->allowed_drop_modes_v4_normal = this->config_json.get_int("AllowedDropModesV4Normal", 0x1D); - this->allowed_drop_modes_v4_battle = this->config_json.get_int("AllowedDropModesV4Battle", 0x05); - this->allowed_drop_modes_v4_challenge = this->config_json.get_int("AllowedDropModesV4Challenge", 0x05); - this->default_drop_mode_v1_v2_normal = this->config_json.get_enum("DefaultDropModeV1V2Normal", Lobby::DropMode::CLIENT); - this->default_drop_mode_v1_v2_battle = this->config_json.get_enum("DefaultDropModeV1V2Battle", Lobby::DropMode::CLIENT); - this->default_drop_mode_v1_v2_challenge = this->config_json.get_enum("DefaultDropModeV1V2Challenge", Lobby::DropMode::CLIENT); - this->default_drop_mode_v3_normal = this->config_json.get_enum("DefaultDropModeV3Normal", Lobby::DropMode::CLIENT); - this->default_drop_mode_v3_battle = this->config_json.get_enum("DefaultDropModeV3Battle", Lobby::DropMode::CLIENT); - this->default_drop_mode_v3_challenge = this->config_json.get_enum("DefaultDropModeV3Challenge", Lobby::DropMode::CLIENT); - this->default_drop_mode_v4_normal = this->config_json.get_enum("DefaultDropModeV4Normal", Lobby::DropMode::SERVER_SHARED); - this->default_drop_mode_v4_battle = this->config_json.get_enum("DefaultDropModeV4Battle", Lobby::DropMode::SERVER_SHARED); - this->default_drop_mode_v4_challenge = this->config_json.get_enum("DefaultDropModeV4Challenge", Lobby::DropMode::SERVER_SHARED); + this->ip_stack_debug = this->config_json->get_bool("IPStackDebug", false); + this->allow_unregistered_users = this->config_json->get_bool("AllowUnregisteredUsers", false); + this->allow_pc_nte = this->config_json->get_bool("AllowPCNTE", false); + this->use_temp_licenses_for_prototypes = this->config_json->get_bool("UseTemporaryLicensesForPrototypes", true); + this->allowed_drop_modes_v1_v2_normal = this->config_json->get_int("AllowedDropModesV1V2Normal", 0x1F); + this->allowed_drop_modes_v1_v2_battle = this->config_json->get_int("AllowedDropModesV1V2Battle", 0x07); + this->allowed_drop_modes_v1_v2_challenge = this->config_json->get_int("AllowedDropModesV1V2Challenge", 0x07); + this->allowed_drop_modes_v3_normal = this->config_json->get_int("AllowedDropModesV3Normal", 0x1F); + this->allowed_drop_modes_v3_battle = this->config_json->get_int("AllowedDropModesV3Battle", 0x07); + this->allowed_drop_modes_v3_challenge = this->config_json->get_int("AllowedDropModesV3Challenge", 0x07); + this->allowed_drop_modes_v4_normal = this->config_json->get_int("AllowedDropModesV4Normal", 0x1D); + this->allowed_drop_modes_v4_battle = this->config_json->get_int("AllowedDropModesV4Battle", 0x05); + this->allowed_drop_modes_v4_challenge = this->config_json->get_int("AllowedDropModesV4Challenge", 0x05); + this->default_drop_mode_v1_v2_normal = this->config_json->get_enum("DefaultDropModeV1V2Normal", Lobby::DropMode::CLIENT); + this->default_drop_mode_v1_v2_battle = this->config_json->get_enum("DefaultDropModeV1V2Battle", Lobby::DropMode::CLIENT); + this->default_drop_mode_v1_v2_challenge = this->config_json->get_enum("DefaultDropModeV1V2Challenge", Lobby::DropMode::CLIENT); + this->default_drop_mode_v3_normal = this->config_json->get_enum("DefaultDropModeV3Normal", Lobby::DropMode::CLIENT); + this->default_drop_mode_v3_battle = this->config_json->get_enum("DefaultDropModeV3Battle", Lobby::DropMode::CLIENT); + this->default_drop_mode_v3_challenge = this->config_json->get_enum("DefaultDropModeV3Challenge", Lobby::DropMode::CLIENT); + this->default_drop_mode_v4_normal = this->config_json->get_enum("DefaultDropModeV4Normal", Lobby::DropMode::SERVER_SHARED); + this->default_drop_mode_v4_battle = this->config_json->get_enum("DefaultDropModeV4Battle", Lobby::DropMode::SERVER_SHARED); + this->default_drop_mode_v4_challenge = this->config_json->get_enum("DefaultDropModeV4Challenge", Lobby::DropMode::SERVER_SHARED); if ((this->default_drop_mode_v4_normal == Lobby::DropMode::CLIENT) || (this->default_drop_mode_v4_battle == Lobby::DropMode::CLIENT) || (this->default_drop_mode_v4_challenge == Lobby::DropMode::CLIENT)) { @@ -710,20 +714,20 @@ void ServerState::load_config_early() { this->quest_flag_persist_mask.update_all(true); try { - for (const auto& flag_id_json : this->config_json.get_list("PreventPersistQuestFlags")) { + for (const auto& flag_id_json : this->config_json->get_list("PreventPersistQuestFlags")) { this->quest_flag_persist_mask.clear(flag_id_json->as_int()); } } catch (const out_of_range&) { } - this->persistent_game_idle_timeout_usecs = this->config_json.get_int("PersistentGameIdleTimeout", 0); + this->persistent_game_idle_timeout_usecs = this->config_json->get_int("PersistentGameIdleTimeout", 0); this->cheat_mode_behavior = parse_behavior_switch("CheatModeBehavior", BehaviorSwitch::OFF_BY_DEFAULT); - this->default_rare_notifs_enabled_v1_v2 = this->config_json.get_bool("RareNotificationsEnabledByDefault", false); + this->default_rare_notifs_enabled_v1_v2 = this->config_json->get_bool("RareNotificationsEnabledByDefault", false); this->default_rare_notifs_enabled_v3_v4 = this->default_rare_notifs_enabled_v1_v2; - this->default_rare_notifs_enabled_v1_v2 = this->config_json.get_bool("RareNotificationsEnabledByDefaultV1V2", this->default_rare_notifs_enabled_v1_v2); - this->default_rare_notifs_enabled_v3_v4 = this->config_json.get_bool("RareNotificationsEnabledByDefaultV3V4", this->default_rare_notifs_enabled_v3_v4); - this->ep3_send_function_call_enabled = this->config_json.get_bool("EnableEpisode3SendFunctionCall", false); - this->catch_handler_exceptions = this->config_json.get_bool("CatchHandlerExceptions", true); + this->default_rare_notifs_enabled_v1_v2 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV1V2", this->default_rare_notifs_enabled_v1_v2); + this->default_rare_notifs_enabled_v3_v4 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV3V4", this->default_rare_notifs_enabled_v3_v4); + this->ep3_send_function_call_enabled = this->config_json->get_bool("EnableEpisode3SendFunctionCall", false); + this->catch_handler_exceptions = this->config_json->get_bool("CatchHandlerExceptions", true); auto parse_int_list = +[](const JSON& json) -> vector { vector ret; @@ -733,27 +737,27 @@ void ServerState::load_config_early() { return ret; }; - this->ep3_infinite_meseta = this->config_json.get_bool("Episode3InfiniteMeseta", false); + this->ep3_infinite_meseta = this->config_json->get_bool("Episode3InfiniteMeseta", false); try { - this->ep3_defeat_player_meseta_rewards = parse_int_list(this->config_json.at("Episode3DefeatPlayerMeseta")); + this->ep3_defeat_player_meseta_rewards = parse_int_list(this->config_json->at("Episode3DefeatPlayerMeseta")); } catch (const out_of_range&) { this->ep3_defeat_player_meseta_rewards = {300, 400, 500, 600, 700}; } try { - this->ep3_defeat_com_meseta_rewards = parse_int_list(this->config_json.get("Episode3DefeatCOMMeseta", JSON::list())); + this->ep3_defeat_com_meseta_rewards = parse_int_list(this->config_json->get("Episode3DefeatCOMMeseta", JSON::list())); } catch (const out_of_range&) { this->ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500}; } - this->ep3_final_round_meseta_bonus = this->config_json.get_int("Episode3FinalRoundMesetaBonus", 300); - this->ep3_jukebox_is_free = this->config_json.get_bool("Episode3JukeboxIsFree", false); - this->ep3_behavior_flags = this->config_json.get_int("Episode3BehaviorFlags", false); - this->ep3_card_auction_points = this->config_json.get_int("CardAuctionPoints", 0); - this->hide_download_commands = this->config_json.get_bool("HideDownloadCommands", true); - this->proxy_allow_save_files = this->config_json.get_bool("ProxyAllowSaveFiles", true); - this->proxy_enable_login_options = this->config_json.get_bool("ProxyEnableLoginOptions", false); + this->ep3_final_round_meseta_bonus = this->config_json->get_int("Episode3FinalRoundMesetaBonus", 300); + this->ep3_jukebox_is_free = this->config_json->get_bool("Episode3JukeboxIsFree", false); + this->ep3_behavior_flags = this->config_json->get_int("Episode3BehaviorFlags", false); + this->ep3_card_auction_points = this->config_json->get_int("CardAuctionPoints", 0); + this->hide_download_commands = this->config_json->get_bool("HideDownloadCommands", true); + this->proxy_allow_save_files = this->config_json->get_bool("ProxyAllowSaveFiles", true); + this->proxy_enable_login_options = this->config_json->get_bool("ProxyEnableLoginOptions", false); try { - const auto& i = this->config_json.at("CardAuctionSize"); + const auto& i = this->config_json->at("CardAuctionSize"); if (i.is_int()) { this->ep3_card_auction_min_size = i.as_int(); this->ep3_card_auction_max_size = this->ep3_card_auction_min_size; @@ -768,7 +772,7 @@ void ServerState::load_config_early() { if (!this->is_replay) { this->ep3_lobby_banners.clear(); - for (const auto& it : this->config_json.get("Episode3LobbyBanners", JSON::list()).as_list()) { + for (const auto& it : this->config_json->get("Episode3LobbyBanners", JSON::list()).as_list()) { string path = "system/ep3/banners/" + it->at(2).as_string(); string compressed_gvm_data; @@ -820,7 +824,7 @@ void ServerState::load_config_early() { } return ret; }; - const auto& categories_json = this->config_json.at("Episode3EXResultValues"); + const auto& categories_json = this->config_json->at("Episode3EXResultValues"); this->ep3_default_ex_values = parse_ep3_ex_result_cmd(categories_json.at("Default")); try { this->ep3_tournament_ex_values = parse_ep3_ex_result_cmd(categories_json.at("Tournament")); @@ -835,7 +839,7 @@ void ServerState::load_config_early() { } try { - const auto& stack_limits_tables_json = this->config_json.at("ItemStackLimits"); + const auto& stack_limits_tables_json = this->config_json->at("ItemStackLimits"); for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) { try { Version v = static_cast(v_s); @@ -863,25 +867,25 @@ void ServerState::load_config_early() { } } - this->bb_global_exp_multiplier = this->config_json.get_int("BBGlobalEXPMultiplier", 1); + this->bb_global_exp_multiplier = this->config_json->get_int("BBGlobalEXPMultiplier", 1); - set_log_levels_from_json(this->config_json.get("LogLevels", JSON::dict())); + set_log_levels_from_json(this->config_json->get("LogLevels", JSON::dict())); try { - this->run_shell_behavior = this->config_json.at("RunInteractiveShell").as_bool() + this->run_shell_behavior = this->config_json->at("RunInteractiveShell").as_bool() ? ServerState::RunShellBehavior::ALWAYS : ServerState::RunShellBehavior::NEVER; } catch (const out_of_range&) { } - this->allow_dc_pc_games = this->config_json.get_bool("AllowDCPCGames", true); - this->allow_gc_xb_games = this->config_json.get_bool("AllowGCXBGames", true); + this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true); + this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true); for (auto& order : this->public_lobby_search_orders) { order.clear(); } try { - const auto& orders_json = this->config_json.get_list("LobbySearchOrders"); + const auto& orders_json = this->config_json->get_list("LobbySearchOrders"); for (size_t v_s = 0; v_s < orders_json.size(); v_s++) { auto& order = this->public_lobby_search_orders.at(v_s); const auto& order_json = orders_json.at(v_s); @@ -899,7 +903,7 @@ void ServerState::load_config_early() { } } try { - const auto& events_json = this->config_json.get_list("LobbyEvents"); + const auto& events_json = this->config_json->get_list("LobbyEvents"); for (size_t z = 0; z < events_json.size(); z++) { const auto& v = events_json.at(z); uint8_t event = v->is_int() ? v->as_int() : event_for_name(v->as_string()); @@ -914,15 +918,15 @@ void ServerState::load_config_early() { this->pre_lobby_event = 0; try { - auto v = this->config_json.at("MenuEvent"); + auto v = this->config_json->at("MenuEvent"); this->pre_lobby_event = v.is_int() ? v.as_int() : event_for_name(v.as_string()); } catch (const out_of_range&) { } - this->ep3_menu_song = this->config_json.get_int("Episode3MenuSong", -1); + this->ep3_menu_song = this->config_json->get_int("Episode3MenuSong", -1); try { - this->quest_category_index = make_shared(this->config_json.at("QuestCategories")); + this->quest_category_index = make_shared(this->config_json->at("QuestCategories")); } catch (const exception& e) { throw runtime_error(string_printf( "QuestCategories is missing or invalid in config.json (%s) - see config.example.json for an example", e.what())); @@ -941,9 +945,9 @@ void ServerState::load_config_early() { "Return to the\nmain menu", MenuItem::Flag::INVISIBLE_IN_INFO_MENU); { auto blank_json = JSON::list(); - const JSON& default_json = this->config_json.get("InformationMenuContents", blank_json); - const JSON& v2_json = this->config_json.get("InformationMenuContentsV1V2", default_json); - const JSON& v3_json = this->config_json.get("InformationMenuContentsV3", default_json); + const JSON& default_json = this->config_json->get("InformationMenuContents", blank_json); + const JSON& v2_json = this->config_json->get("InformationMenuContentsV1V2", default_json); + const JSON& v3_json = this->config_json->get("InformationMenuContentsV3", default_json); uint32_t item_id = 0; for (const auto& item : v2_json.as_list()) { @@ -974,7 +978,7 @@ void ServerState::load_config_early() { try { map sorted_jsons; - for (const auto& it : this->config_json.at(key).as_dict()) { + for (const auto& it : this->config_json->at(key).as_dict()) { sorted_jsons.emplace(it.first, *it.second); } @@ -1000,7 +1004,7 @@ void ServerState::load_config_early() { this->proxy_destinations_menu_xb = generate_proxy_destinations_menu(this->proxy_destinations_xb, "ProxyDestinations-XB"); try { - const string& netloc_str = this->config_json.get_string("ProxyDestination-Patch"); + const string& netloc_str = this->config_json->get_string("ProxyDestination-Patch"); this->proxy_destination_patch = parse_netloc(netloc_str); config_log.info("Patch server proxy is enabled with destination %s", netloc_str.c_str()); for (auto& it : this->name_to_port_config) { @@ -1013,7 +1017,7 @@ void ServerState::load_config_early() { this->proxy_destination_patch.second = 0; } try { - const string& netloc_str = this->config_json.get_string("ProxyDestination-BB"); + const string& netloc_str = this->config_json->get_string("ProxyDestination-BB"); this->proxy_destination_bb = parse_netloc(netloc_str); config_log.info("BB proxy is enabled with destination %s", netloc_str.c_str()); for (auto& it : this->name_to_port_config) { @@ -1026,13 +1030,13 @@ void ServerState::load_config_early() { this->proxy_destination_bb.second = 0; } - this->welcome_message = this->config_json.get_string("WelcomeMessage", ""); - this->pc_patch_server_message = this->config_json.get_string("PCPatchServerMessage", ""); - this->bb_patch_server_message = this->config_json.get_string("BBPatchServerMessage", ""); + this->welcome_message = this->config_json->get_string("WelcomeMessage", ""); + this->pc_patch_server_message = this->config_json->get_string("PCPatchServerMessage", ""); + this->bb_patch_server_message = this->config_json->get_string("BBPatchServerMessage", ""); this->team_reward_defs_json = nullptr; try { - this->team_reward_defs_json = std::move(this->config_json.at("TeamRewards")); + this->team_reward_defs_json = std::move(this->config_json->at("TeamRewards")); } catch (const out_of_range&) { } @@ -1041,14 +1045,14 @@ void ServerState::load_config_early() { try { string key = "RareEnemyRates-"; key += token_name_for_difficulty(z); - this->rare_enemy_rates_by_difficulty[z] = make_shared(this->config_json.at(key)); + this->rare_enemy_rates_by_difficulty[z] = make_shared(this->config_json->at(key)); prev = this->rare_enemy_rates_by_difficulty[z]; } catch (const out_of_range&) { this->rare_enemy_rates_by_difficulty[z] = prev; } } try { - this->rare_enemy_rates_challenge = make_shared(this->config_json.at("RareEnemyRates-Challenge")); + this->rare_enemy_rates_challenge = make_shared(this->config_json->at("RareEnemyRates-Challenge")); } catch (const out_of_range&) { this->rare_enemy_rates_challenge = Map::DEFAULT_RARE_ENEMIES; } @@ -1057,7 +1061,7 @@ void ServerState::load_config_early() { this->min_levels_v4[1] = DEFAULT_MIN_LEVELS_V4_EP2; this->min_levels_v4[2] = DEFAULT_MIN_LEVELS_V4_EP4; try { - for (const auto& ep_it : this->config_json.get_dict("BBMinimumLevels")) { + for (const auto& ep_it : this->config_json->get_dict("BBMinimumLevels")) { array levels({0, 0, 0, 0}); for (size_t z = 0; z < 4; z++) { levels[z] = ep_it.second->get_int(z) - 1; @@ -1083,7 +1087,7 @@ void ServerState::load_config_early() { void ServerState::load_config_late() { this->ep3_card_auction_pool.clear(); try { - for (const auto& it : this->config_json.get_dict("CardAuctionPool")) { + for (const auto& it : this->config_json->get_dict("CardAuctionPool")) { uint16_t card_id; try { card_id = this->ep3_card_index->definition_for_name_normalized(it.first)->def.card_id; @@ -1104,7 +1108,7 @@ void ServerState::load_config_late() { } if (this->ep3_card_index) { try { - const auto& ep3_trap_cards_json = this->config_json.get_list("Episode3TrapCards"); + const auto& ep3_trap_cards_json = this->config_json->get_list("Episode3TrapCards"); if (!ep3_trap_cards_json.empty()) { if (ep3_trap_cards_json.size() != 5) { throw runtime_error("Episode3TrapCards must be a list of 5 lists"); @@ -1137,7 +1141,7 @@ void ServerState::load_config_late() { this->secret_lottery_results.clear(); if (this->item_name_index(Version::BB_V4)) { try { - for (const auto& type_it : this->config_json.get_list("QuestF95EResultItems")) { + for (const auto& type_it : this->config_json->get_list("QuestF95EResultItems")) { auto& type_res = this->quest_F95E_results.emplace_back(); for (const auto& difficulty_it : type_it->as_list()) { auto& difficulty_res = type_res.emplace_back(); @@ -1149,7 +1153,7 @@ void ServerState::load_config_late() { } catch (const out_of_range&) { } try { - for (const auto& it : this->config_json.get_list("QuestF95FResultItems")) { + for (const auto& it : this->config_json->get_list("QuestF95FResultItems")) { auto& list = it->as_list(); size_t price = list.at(0)->as_int(); this->quest_F95F_results.emplace_back(make_pair(price, this->parse_item_description(Version::BB_V4, list.at(1)->as_string()))); @@ -1157,14 +1161,14 @@ void ServerState::load_config_late() { } catch (const out_of_range&) { } try { - this->quest_F960_failure_results = QuestF960Result(this->config_json.at("QuestF960FailureResultItems"), this->item_name_index(Version::BB_V4)); - for (const auto& it : this->config_json.get_list("QuestF960SuccessResultItems")) { + this->quest_F960_failure_results = QuestF960Result(this->config_json->at("QuestF960FailureResultItems"), this->item_name_index(Version::BB_V4)); + for (const auto& it : this->config_json->get_list("QuestF960SuccessResultItems")) { this->quest_F960_success_results.emplace_back(*it, this->item_name_index(Version::BB_V4)); } } catch (const out_of_range&) { } try { - for (const auto& it : this->config_json.get_list("SecretLotteryResultItems")) { + for (const auto& it : this->config_json->get_list("SecretLotteryResultItems")) { this->secret_lottery_results.emplace_back(this->parse_item_description(Version::BB_V4, it->as_string())); } } catch (const out_of_range&) { diff --git a/src/ServerState.hh b/src/ServerState.hh index 4e75024c..5c75cacf 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -66,7 +66,7 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr base; std::string config_filename; - JSON config_json; + std::shared_ptr config_json; bool is_replay = false; bool one_time_config_loaded = false; bool default_lobbies_created = false; @@ -285,7 +285,8 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr item_parameter_table(Version version) const; std::shared_ptr item_parameter_table_for_encode(Version version) const; std::shared_ptr item_stack_limits(Version version) const; - std::shared_ptr item_name_index(Version version) const; + std::shared_ptr item_name_index_opt(Version version) const; // Returns null if missing + std::shared_ptr item_name_index(Version version) const; // Throws if missing std::string describe_item(Version version, const ItemData& item, bool include_color_codes) const; ItemData parse_item_description(Version version, const std::string& description) const;