rewrite ServerState dependency management

This commit is contained in:
Martin Michelsen
2023-12-31 21:25:39 -08:00
parent a24d0ad703
commit f479f586cb
7 changed files with 420 additions and 349 deletions
+2 -2
View File
@@ -123,10 +123,10 @@ set(SOURCES
src/ServerState.cc
src/Shell.cc
src/StaticGameData.cc
src/StepGraph.cc
src/TeamIndex.cc
src/Text.cc
src/TextArchive.cc
src/UnicodeTextSet.cc
src/TextIndex.cc
src/Version.cc
src/WordSelectTable.cc
)
+37 -48
View File
@@ -1493,24 +1493,20 @@ Action a_show_ep3_cards(
+[](Arguments& args) {
bool one_line = args.get<bool>("one-line");
Episode3::CardIndex card_index(
"system/ep3/card-definitions.mnr",
"system/ep3/card-definitions.mnrd",
"system/ep3/card-text.mnr",
"system/ep3/card-text.mnrd",
"system/ep3/card-dice-text.mnr",
"system/ep3/card-dice-text.mnrd");
unique_ptr<TextArchive> text_english;
ServerState s;
s.load_objects("ep3_data");
unique_ptr<BinaryTextSet> text_english;
try {
JSON json = JSON::parse(load_file("system/ep3/text-english.json"));
text_english = make_unique<TextArchive>(json);
text_english = make_unique<BinaryTextSet>(json);
} catch (const exception& e) {
}
auto card_ids = card_index.all_ids();
auto card_ids = s.ep3_card_index->all_ids();
log_info("%zu card definitions", card_ids.size());
for (uint32_t card_id : card_ids) {
auto entry = card_index.definition_for_id(card_id);
auto entry = s.ep3_card_index->definition_for_id(card_id);
string s = entry->def.str(one_line, text_english.get());
if (one_line) {
fprintf(stdout, "%s\n", s.c_str());
@@ -1544,18 +1540,14 @@ Action a_generate_ep3_cards_html(
+[](Arguments& args) {
size_t num_threads = args.get<size_t>("threads", 0);
Episode3::CardIndex card_index(
"system/ep3/card-definitions.mnr",
"system/ep3/card-definitions.mnrd",
"system/ep3/card-text.mnr",
"system/ep3/card-text.mnrd",
"system/ep3/card-dice-text.mnr",
"system/ep3/card-dice-text.mnrd");
unique_ptr<TextArchive> text_english;
ServerState s;
s.load_objects("ep3_data");
s.load_objects("text_index");
shared_ptr<const TextSet> text_english;
try {
JSON json = JSON::parse(load_file("system/ep3/text-english.json"));
text_english = make_unique<TextArchive>(json);
} catch (const exception& e) {
text_english = s.text_index->get(Version::GC_EP3, 1);
} catch (const out_of_range&) {
}
struct CardInfo {
@@ -1572,11 +1564,11 @@ Action a_generate_ep3_cards_html(
}
};
vector<CardInfo> infos;
for (uint32_t card_id : card_index.all_ids()) {
for (uint32_t card_id : s.ep3_card_index->all_ids()) {
if (infos.size() <= card_id) {
infos.resize(card_id + 1);
}
infos[card_id].ce = card_index.definition_for_id(card_id);
infos[card_id].ce = s.ep3_card_index->definition_for_id(card_id);
}
for (const auto& filename : list_directory_sorted("system/ep3/cardtex")) {
if ((filename[0] == 'C' || filename[0] == 'M' || filename[0] == 'L') && (filename[1] == '_')) {
@@ -1684,20 +1676,21 @@ Action a_show_ep3_maps(
human-readable format.\n",
+[](Arguments&) {
config_log.info("Collecting Episode 3 data");
Episode3::MapIndex map_index("system/ep3/maps");
Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd");
auto map_ids = map_index.all_numbers();
ServerState s;
s.load_objects("ep3_data");
auto map_ids = s.ep3_map_index->all_numbers();
log_info("%zu maps", map_ids.size());
for (uint32_t map_id : map_ids) {
auto map = map_index.for_number(map_id);
auto map = s.ep3_map_index->for_number(map_id);
const auto& vms = map->all_versions();
for (size_t language = 0; language < vms.size(); language++) {
if (!vms[language]) {
continue;
}
string s = vms[language]->map->str(&card_index, language);
fprintf(stdout, "(%c) %s\n", char_for_language_code(language), s.c_str());
string map_s = vms[language]->map->str(s.ep3_card_index.get(), language);
fprintf(stdout, "(%c) %s\n", char_for_language_code(language), map_s.c_str());
}
}
});
@@ -1708,26 +1701,21 @@ Action a_show_battle_params(
Print the Blue Burst battle parameters from the system/blueburst directory\n\
in a human-readable format.\n",
+[](Arguments&) {
BattleParamsIndex index(
make_shared<string>(load_file("system/blueburst/BattleParamEntry_on.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_lab_on.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_ep4_on.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_lab.dat")),
make_shared<string>(load_file("system/blueburst/BattleParamEntry_ep4.dat")));
ServerState s;
s.load_objects("battle_params");
fprintf(stdout, "Episode 1 multi\n");
index.get_table(false, Episode::EP1).print(stdout);
s.battle_params->get_table(false, Episode::EP1).print(stdout);
fprintf(stdout, "Episode 1 solo\n");
index.get_table(true, Episode::EP1).print(stdout);
s.battle_params->get_table(true, Episode::EP1).print(stdout);
fprintf(stdout, "Episode 2 multi\n");
index.get_table(false, Episode::EP2).print(stdout);
s.battle_params->get_table(false, Episode::EP2).print(stdout);
fprintf(stdout, "Episode 2 solo\n");
index.get_table(true, Episode::EP2).print(stdout);
s.battle_params->get_table(true, Episode::EP2).print(stdout);
fprintf(stdout, "Episode 4 multi\n");
index.get_table(false, Episode::EP4).print(stdout);
s.battle_params->get_table(false, Episode::EP4).print(stdout);
fprintf(stdout, "Episode 4 solo\n");
index.get_table(true, Episode::EP4).print(stdout);
s.battle_params->get_table(true, Episode::EP4).print(stdout);
});
Action a_parse_object_graph(
@@ -1848,12 +1836,13 @@ Action a_diff_dol_files(
Action a_replay_ep3_battle_commands(
"replay-ep3-battle-commands", nullptr, +[](Arguments& args) {
auto card_index = make_shared<Episode3::CardIndex>("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd");
auto map_index = make_shared<Episode3::MapIndex>("system/ep3/maps");
ServerState s;
s.load_objects("ep3_data");
auto random_crypt = make_shared<PSOV2Encryption>(args.get<uint32_t>("seed", 0, Arguments::IntFormat::HEX));
Episode3::Server::Options options = {
.card_index = card_index,
.map_index = map_index,
.card_index = s.ep3_card_index,
.map_index = s.ep3_map_index,
.behavior_flags = 0x0092,
.random_crypt = random_crypt,
.tournament = nullptr,
@@ -1897,7 +1886,7 @@ Action a_run_server_replay_log(
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
auto state = make_shared<ServerState>(base, config_filename, is_replay);
state->init();
state->load_objects("all");
shared_ptr<DNSServer> dns_server;
if (state->dns_server_port && !is_replay) {
+23 -43
View File
@@ -120,17 +120,24 @@ General commands:\n\
\n\
Server commands:\n\
reload ITEM [ITEM...]\n\
Reload various parts of the server configuration. ITEMs can be:\n\
licenses - reload the license index file\n\
patches - reindex the PC and BB patch directories\n\
battle-params - reload the enemy stats files\n\
level-table - reload the level-up tables\n\
item-tables - reload the item generation tables\n\
ep3 - reload Episode 3 card definitions and maps (not download quests)\n\
quests - reindex all quests (including Episode 3 download quests)\n\
functions - recompile all client-side functions\n\
dol-files - reindex all DOL files\n\
Reload various parts of the server configuration. When you reload any item,\n\
any other item that depends on it will be reloaded as well. The items are:\n\
all - reindex/reload everything\n\
battle-params - reload the BB enemy stats files\n\
bb-private-keys - reload BB private keys\n\
config - reload most fields from config.json\n\
dol-files - reindex all DOL files\n\
drop-tables - reload drop tables\n\
ep3-data - reload Episode 3 cards and maps (not download quests)\n\
functions - recompile all client-side patches and functions\n\
item-definitions - reload item definitions files\n\
level-table - reload the level-up tables\n\
licenses - reindex user licenses\n\
patch-indexes - reindex the PC and BB patch directories\n\
quest-index - reindex all quests (including Episode3 download quests)\n\
teams - reindex all BB teams\n\
text-index - reload in-game text\n\
word-select-table - regenerate the Word Select translation table\n\
Reloading will not affect items that are in use; for example, if an Episode\n\
3 battle is in progress, it will continue to use the previous map and card\n\
definitions. Similarly, BB clients are not forced to disconnect or reload\n\
@@ -285,41 +292,14 @@ Proxy session commands:\n\
if (types.empty()) {
throw invalid_argument("no data type given");
}
for (const string& type : types) {
if (type == "licenses") {
this->state->load_licenses();
} else if (type == "teams") {
this->state->load_teams();
} else if (type == "patches") {
this->state->load_patch_indexes();
} else if (type == "battle-params") {
this->state->load_battle_params();
} else if (type == "level-table") {
this->state->load_level_table();
} else if (type == "item-tables") {
this->state->load_item_name_index();
this->state->load_item_tables();
} else if (type == "word-select") {
this->state->load_word_select_table();
} else if (type == "ep3") {
this->state->load_ep3_data();
} else if (type == "quests") {
this->state->load_quest_index();
} else if (type == "functions") {
auto config_json = this->state->load_config();
this->state->compile_functions();
} else if (type == "dol-files") {
auto config_json = this->state->load_config();
this->state->load_dol_files();
} else if (type == "config") {
auto config_json = this->state->load_config();
this->state->parse_config(config_json, true);
this->state->resolve_ep3_card_names();
this->state->load_teams();
} else {
throw invalid_argument("incorrect data type");
for (auto& type : types) {
for (char& ch : type) {
if (ch == '-') {
ch = '_';
}
}
}
this->state->load_objects(types);
} else if (command_name == "add-license") {
auto l = this->state->license_index->create_license();
+194 -198
View File
@@ -25,137 +25,31 @@ ServerState::QuestF960Result::QuestF960Result(const JSON& json, std::shared_ptr<
this->probability_upgrade = json.get_int("ProbabilityUpgrade", 0);
for (size_t day = 0; day < 7; day++) {
for (const auto& item_it : json.get_list(day_names[day])) {
this->results[day].emplace_back(name_index->parse_item_description(Version::BB_V4, item_it->as_string()));
this->results[day].emplace_back(name_index->parse_item_description(item_it->as_string()));
}
}
}
ServerState::ServerState() : creation_time(now()) {
this->create_load_step_graph();
}
ServerState::ServerState(shared_ptr<struct event_base> base, const string& config_filename, bool is_replay)
: creation_time(now()),
base(base),
config_filename(config_filename),
is_replay(is_replay),
dns_server_port(0),
ip_stack_debug(false),
allow_unregistered_users(false),
allow_pc_nte(false),
use_temp_licenses_for_prototypes(true),
allow_dc_pc_games(false),
allow_gc_xb_games(true),
allowed_drop_modes_v1_v2_normal(0x1F),
allowed_drop_modes_v1_v2_battle(0x07),
allowed_drop_modes_v1_v2_challenge(0x07),
allowed_drop_modes_v3_normal(0x1F),
allowed_drop_modes_v3_battle(0x07),
allowed_drop_modes_v3_challenge(0x07),
allowed_drop_modes_v4_normal(0x1D), // CLIENT not allowed
allowed_drop_modes_v4_battle(0x05),
allowed_drop_modes_v4_challenge(0x05),
default_drop_mode_v1_v2_normal(Lobby::DropMode::CLIENT),
default_drop_mode_v1_v2_battle(Lobby::DropMode::CLIENT),
default_drop_mode_v1_v2_challenge(Lobby::DropMode::CLIENT),
default_drop_mode_v3_normal(Lobby::DropMode::CLIENT),
default_drop_mode_v3_battle(Lobby::DropMode::CLIENT),
default_drop_mode_v3_challenge(Lobby::DropMode::CLIENT),
default_drop_mode_v4_normal(Lobby::DropMode::SERVER_SHARED),
default_drop_mode_v4_battle(Lobby::DropMode::SERVER_SHARED),
default_drop_mode_v4_challenge(Lobby::DropMode::SERVER_SHARED),
persistent_game_idle_timeout_usecs(0),
ep3_send_function_call_enabled(false),
catch_handler_exceptions(true),
ep3_infinite_meseta(false),
ep3_defeat_player_meseta_rewards({400, 500, 600, 700, 800}),
ep3_defeat_com_meseta_rewards({100, 200, 300, 400, 500}),
ep3_final_round_meseta_bonus(300),
ep3_jukebox_is_free(false),
ep3_behavior_flags(0),
hide_download_commands(true),
run_shell_behavior(RunShellBehavior::DEFAULT),
cheat_mode_behavior(BehaviorSwitch::OFF_BY_DEFAULT),
bb_global_exp_multiplier(1),
ep3_card_auction_points(0),
ep3_card_auction_min_size(0),
ep3_card_auction_max_size(0),
player_files_manager(make_shared<PlayerFilesManager>(base)),
destroy_lobbies_event(event_new(base.get(), -1, EV_TIMEOUT, &ServerState::dispatch_destroy_lobbies, this), event_free),
next_lobby_id(1),
pre_lobby_event(0),
ep3_menu_song(-1),
local_address(0),
external_address(0),
proxy_allow_save_files(true),
proxy_enable_login_options(false) {}
player_files_manager(this->base ? make_shared<PlayerFilesManager>(base) : nullptr),
destroy_lobbies_event(this->base ? event_new(base.get(), -1, EV_TIMEOUT, &ServerState::dispatch_destroy_lobbies, this) : nullptr, event_free) {
this->create_load_step_graph();
}
void ServerState::init() {
vector<shared_ptr<Lobby>> non_v1_only_lobbies;
vector<shared_ptr<Lobby>> ep3_only_lobbies;
void ServerState::load_objects(const std::string& what) {
this->load_step_graph.run(what);
}
for (size_t x = 0; x < 20; x++) {
auto lobby_name = string_printf("LOBBY%zu", x + 1);
bool allow_v1 = (x <= 9);
bool allow_non_ep3 = (x <= 14);
shared_ptr<Lobby> l = this->create_lobby(false);
l->set_flag(Lobby::Flag::PUBLIC);
l->set_flag(Lobby::Flag::DEFAULT);
l->set_flag(Lobby::Flag::PERSISTENT);
if (allow_non_ep3) {
if (allow_v1) {
l->allow_version(Version::DC_NTE);
l->allow_version(Version::DC_V1_11_2000_PROTOTYPE);
l->allow_version(Version::DC_V1);
}
l->allow_version(Version::DC_V2);
l->allow_version(Version::PC_NTE);
l->allow_version(Version::PC_V2);
l->allow_version(Version::GC_NTE);
l->allow_version(Version::GC_V3);
l->allow_version(Version::XB_V3);
l->allow_version(Version::BB_V4);
}
l->allow_version(Version::GC_EP3_NTE);
l->allow_version(Version::GC_EP3);
l->block = x + 1;
l->name = lobby_name;
l->max_clients = 12;
if (!allow_non_ep3) {
l->episode = Episode::EP3;
}
if (allow_non_ep3) {
this->public_lobby_search_order.emplace_back(l);
} else {
ep3_only_lobbies.emplace_back(l);
}
}
// Annoyingly, the CARD lobbies should be searched first, but are sent at the
// end of the lobby list command, so we have to change the search order
// manually here.
this->public_lobby_search_order.insert(
this->public_lobby_search_order.begin(),
ep3_only_lobbies.begin(),
ep3_only_lobbies.end());
// Load all the necessary data
auto config = this->load_config();
this->collect_network_addresses();
this->load_item_name_index();
this->parse_config(config, false);
this->load_bb_private_keys();
this->load_licenses();
this->load_teams();
this->load_patch_indexes();
this->load_battle_params();
this->load_level_table();
this->load_item_tables();
this->load_word_select_table();
this->load_ep3_data();
this->resolve_ep3_card_names();
this->load_quest_index();
this->compile_functions();
this->load_dol_files();
void ServerState::load_objects(const std::vector<std::string>& what) {
this->load_step_graph.run(what);
}
void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
@@ -578,20 +472,6 @@ shared_ptr<const string> ServerState::load_bb_file(
}
}
void ServerState::collect_network_addresses() {
config_log.info("Reading network addresses");
this->all_addresses = get_local_addresses();
for (const auto& it : this->all_addresses) {
string addr_str = string_for_address(it.second);
config_log.info("Found interface: %s = %s", it.first.c_str(), addr_str.c_str());
}
}
JSON ServerState::load_config() const {
config_log.info("Loading configuration");
return JSON::parse(load_file(this->config_filename));
}
static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
vector<PortConfiguration> ret;
for (const auto& item_json_it : json.as_dict()) {
@@ -605,8 +485,22 @@ static vector<PortConfiguration> parse_port_configuration(const JSON& json) {
return ret;
}
void ServerState::parse_config(const JSON& json, bool is_reload) {
config_log.info("Parsing configuration");
void ServerState::collect_network_addresses() {
config_log.info("Reading network addresses");
this->all_addresses = get_local_addresses();
for (const auto& it : this->all_addresses) {
string addr_str = string_for_address(it.second);
config_log.info("Found interface: %s = %s", it.first.c_str(), addr_str.c_str());
}
}
void ServerState::load_config() {
if (this->config_filename.empty()) {
throw logic_error("configuration filename is missing");
}
config_log.info("Loading configuration");
auto json = JSON::parse(load_file(this->config_filename));
auto parse_behavior_switch = [&](const string& json_key, BehaviorSwitch default_value) -> ServerState::BehaviorSwitch {
try {
@@ -629,7 +523,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->name = json.at("ServerName").as_string();
if (!is_reload) {
if (!this->config_loaded) {
try {
this->username = json.at("User").as_string();
if (this->username == "$SUDO_USER") {
@@ -772,12 +666,17 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
try {
for (const auto& it : json.get_dict("CardAuctionPool")) {
uint16_t card_id;
try {
card_id = this->ep3_card_index->definition_for_name_normalized(it.first)->def.card_id;
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in auction pool does not exist", it.first.c_str()));
}
this->ep3_card_auction_pool.emplace_back(
CardAuctionPoolEntry{
.probability = static_cast<uint64_t>(it.second->at(0).as_int()),
.card_id = 0,
.min_price = static_cast<uint16_t>(it.second->at(1).as_int()),
.card_name = it.first});
.card_id = card_id,
.min_price = static_cast<uint16_t>(it.second->at(1).as_int())});
}
} catch (const out_of_range&) {
}
@@ -788,11 +687,18 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
if (ep3_trap_cards_json.size() != 5) {
throw runtime_error("Episode3TrapCards must be a list of 5 lists");
}
this->ep3_trap_card_names.clear();
for (const auto& trap_type_it : ep3_trap_cards_json) {
auto& names = this->ep3_trap_card_names.emplace_back();
for (const auto& card_it : trap_type_it->as_list()) {
names.emplace_back(card_it->as_string());
for (size_t trap_type = 0; trap_type < 5; trap_type++) {
auto& trap_card_ids = this->ep3_trap_card_ids[trap_type];
for (const auto& card_it : ep3_trap_cards_json.at(trap_type)->as_list()) {
try {
const auto& card = this->ep3_card_index->definition_for_name_normalized(card_it->as_string());
if (card->def.type != Episode3::CardType::ASSIST) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str()));
}
trap_card_ids.emplace_back(card->def.card_id);
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str()));
}
}
}
}
@@ -800,6 +706,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
}
if (!this->is_replay) {
this->ep3_lobby_banners.clear();
for (const auto& it : json.get("Episode3LobbyBanners", JSON::list()).as_list()) {
Image img("system/ep3/banners/" + it->at(2).as_string());
string gvm = encode_gvm(img, img.get_has_alpha() ? GVRDataFormat::RGB5A3 : GVRDataFormat::RGB565);
@@ -889,13 +796,11 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
set_log_levels_from_json(json.get("LogLevels", JSON::dict()));
if (!is_reload) {
try {
this->run_shell_behavior = json.at("RunInteractiveShell").as_bool()
? ServerState::RunShellBehavior::ALWAYS
: ServerState::RunShellBehavior::NEVER;
} catch (const out_of_range&) {
}
try {
this->run_shell_behavior = json.at("RunInteractiveShell").as_bool()
? ServerState::RunShellBehavior::ALWAYS
: ServerState::RunShellBehavior::NEVER;
} catch (const out_of_range&) {
}
this->allow_dc_pc_games = json.get_bool("AllowDCPCGames", this->allow_dc_pc_games);
@@ -913,13 +818,11 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->ep3_menu_song = json.get_int("Episode3MenuSong", this->ep3_menu_song);
if (!is_reload) {
try {
this->quest_category_index = make_shared<QuestCategoryIndex>(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()));
}
try {
this->quest_category_index = make_shared<QuestCategoryIndex>(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()));
}
config_log.info("Creating menus");
@@ -1071,6 +974,8 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
}
} catch (const out_of_range&) {
}
this->config_loaded = true;
}
void ServerState::load_bb_private_keys() {
@@ -1093,7 +998,6 @@ void ServerState::load_licenses() {
void ServerState::load_teams() {
config_log.info("Indexing teams");
this->team_index = make_shared<TeamIndex>("system/teams", this->team_reward_defs_json);
this->team_reward_defs_json = nullptr;
}
void ServerState::load_patch_indexes() {
@@ -1393,37 +1297,6 @@ void ServerState::load_ep3_data() {
config_log.info("Loaded Episode 3 tournament state");
}
void ServerState::resolve_ep3_card_names() {
config_log.info("Resolving Episode 3 card names");
for (auto& e : this->ep3_card_auction_pool) {
try {
const auto& card = this->ep3_card_index->definition_for_name_normalized(e.card_name);
e.card_id = card->def.card_id;
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in auction pool does not exist", e.card_name.c_str()));
}
}
for (size_t z = 0; z < this->ep3_trap_card_ids.size(); z++) {
auto& ids = this->ep3_trap_card_ids[z];
ids.clear();
if (z < this->ep3_trap_card_names.size()) {
auto& names = this->ep3_trap_card_names[z];
for (const auto& name : names) {
try {
const auto& card = this->ep3_card_index->definition_for_name_normalized(name);
if (card->def.type != Episode3::CardType::ASSIST) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str()));
}
ids.emplace_back(card->def.card_id);
} catch (const out_of_range&) {
throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str()));
}
}
}
}
}
void ServerState::load_quest_index() {
config_log.info("Collecting quests");
this->default_quest_index = make_shared<QuestIndex>("system/quests", this->quest_category_index, false);
@@ -1441,14 +1314,137 @@ void ServerState::load_dol_files() {
this->dol_file_index = make_shared<DOLFileIndex>("system/dol");
}
shared_ptr<const vector<string>> ServerState::information_contents_for_client(shared_ptr<const Client> c) const {
return is_v1_or_v2(c->version()) ? this->information_contents_v2 : this->information_contents_v3;
void ServerState::create_default_lobbies() {
if (this->default_lobbies_created) {
return;
}
this->default_lobbies_created = true;
vector<shared_ptr<Lobby>> non_v1_only_lobbies;
vector<shared_ptr<Lobby>> ep3_only_lobbies;
for (size_t x = 0; x < 20; x++) {
auto lobby_name = string_printf("LOBBY%zu", x + 1);
bool allow_v1 = (x <= 9);
bool allow_non_ep3 = (x <= 14);
shared_ptr<Lobby> l = this->create_lobby(false);
l->event = this->pre_lobby_event;
l->set_flag(Lobby::Flag::PUBLIC);
l->set_flag(Lobby::Flag::DEFAULT);
l->set_flag(Lobby::Flag::PERSISTENT);
if (allow_non_ep3) {
if (allow_v1) {
l->allow_version(Version::DC_NTE);
l->allow_version(Version::DC_V1_11_2000_PROTOTYPE);
l->allow_version(Version::DC_V1);
}
l->allow_version(Version::DC_V2);
l->allow_version(Version::PC_NTE);
l->allow_version(Version::PC_V2);
l->allow_version(Version::GC_NTE);
l->allow_version(Version::GC_V3);
l->allow_version(Version::XB_V3);
l->allow_version(Version::BB_V4);
}
l->allow_version(Version::GC_EP3_NTE);
l->allow_version(Version::GC_EP3);
l->block = x + 1;
l->name = lobby_name;
l->max_clients = 12;
if (!allow_non_ep3) {
l->episode = Episode::EP3;
}
if (allow_non_ep3) {
this->public_lobby_search_order.emplace_back(l);
} else {
ep3_only_lobbies.emplace_back(l);
}
}
// Annoyingly, the CARD lobbies should be searched first, but are sent at the
// end of the lobby list command, so we have to change the search order
// manually here.
this->public_lobby_search_order.insert(
this->public_lobby_search_order.begin(),
ep3_only_lobbies.begin(),
ep3_only_lobbies.end());
}
shared_ptr<const QuestIndex> ServerState::quest_index_for_version(Version version) const {
return is_ep3(version) ? this->ep3_download_quest_index : this->default_quest_index;
}
void ServerState::create_load_step_graph() {
this->load_step_graph.add_step("all", {}, nullptr);
void ServerState::dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx) {
reinterpret_cast<ServerState*>(ctx)->lobbies_to_destroy.clear();
// In: none
// Out: all_addresses
this->load_step_graph.add_step("network_addresses", {"all"}, bind(&ServerState::collect_network_addresses, this));
// In: none
// Out: bb_private_keys
this->load_step_graph.add_step("bb_private_keys", {"all"}, bind(&ServerState::load_bb_private_keys, this));
// In: none
// Out: license_index
this->load_step_graph.add_step("licenses", {"all"}, bind(&ServerState::load_licenses, this));
// In: none
// Out: pc_patch_file_index, bb_patch_file_index, bb_data_gsl
this->load_step_graph.add_step("patch_indexes", {"all"}, bind(&ServerState::load_patch_indexes, this));
// In: none
// Out: ep3_map_index, ep3_card_index, ep3_card_index_trial, ep3_com_deck_index, ep3_tournament_index
this->load_step_graph.add_step("ep3_data", {"all"}, bind(&ServerState::load_ep3_data, this));
// In: none
// Out: function_code_index
this->load_step_graph.add_step("functions", {"all"}, bind(&ServerState::compile_functions, this));
// In: none
// Out: dol_file_index
this->load_step_graph.add_step("dol_files", {"all"}, bind(&ServerState::load_dol_files, this));
// In: none
// Out: lobbies
this->load_step_graph.add_step("lobbies", {"all"}, bind(&ServerState::create_default_lobbies, this));
// In: bb_patch_file_index
// Out: battle_params
this->load_step_graph.add_step("battle_params", {"all", "patch_indexes"}, bind(&ServerState::load_battle_params, this));
// In: bb_patch_file_index
// Out: level_table
this->load_step_graph.add_step("level_table", {"all", "patch_indexes"}, bind(&ServerState::load_level_table, this));
// In: bb_patch_file_index
// Out: text_index
this->load_step_graph.add_step("text_index", {"all", "patch_indexes"}, bind(&ServerState::load_text_index, this));
// In: text_index (optional)
// Out: word_select_table
this->load_step_graph.add_step("word_select_table", {"all"}, bind(&ServerState::load_word_select_table, this));
// In: none
// Out: item_parameter_tables, mag_evolution_table
this->load_step_graph.add_step("item_definitions", {"all"}, bind(&ServerState::load_item_definitions, this));
// In: text_index, item_parameter_tables
// Out: item_name_indexes
this->load_step_graph.add_step("item_name_indexes", {"all", "text_index", "item_definitions"}, bind(&ServerState::load_item_name_indexes, this));
// In: none
// Out: rare_item_sets, common_item_sets, armor_random_set, tool_random_set, weapon_random_sets, tekker_adjustment_set
this->load_step_graph.add_step("drop_tables", {"all", "item_definitions", "item_name_indexes"}, bind(&ServerState::load_drop_tables, this));
// In: all_addresses, ep3_card_index, item_name_indexes
// Out: config, ep3_lobby_banners, quest_category_index, information menus, proxy destinations menus, team_reward_defs_json
this->load_step_graph.add_step("config", {"all", "network_addresses", "ep3_data", "item_name_indexes"}, bind(&ServerState::load_config, this));
// In: team_reward_defs_json
// Out: team_index
this->load_step_graph.add_step("teams", {"all", "config"}, bind(&ServerState::load_teams, this));
// In: quest_category_index
// Out: default_quest_index, ep3_download_quest_index
this->load_step_graph.add_step("quest_index", {"all", "config"}, bind(&ServerState::load_quest_index, this));
}
+66 -58
View File
@@ -25,6 +25,7 @@
#include "Menu.hh"
#include "PlayerFilesManager.hh"
#include "Quest.hh"
#include "StepGraph.hh"
#include "TeamIndex.hh"
#include "WordSelectTable.hh"
@@ -63,52 +64,56 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<struct event_base> base;
std::string config_filename;
bool is_replay;
bool is_replay = false;
bool config_loaded = false;
bool default_lobbies_created = false;
StepGraph load_step_graph;
std::string name;
std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username;
uint16_t dns_server_port;
uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses;
bool ip_stack_debug;
bool allow_unregistered_users;
bool allow_pc_nte;
bool use_temp_licenses_for_prototypes;
bool allow_dc_pc_games;
bool allow_gc_xb_games;
uint8_t allowed_drop_modes_v1_v2_normal;
uint8_t allowed_drop_modes_v1_v2_battle;
uint8_t allowed_drop_modes_v1_v2_challenge;
uint8_t allowed_drop_modes_v3_normal;
uint8_t allowed_drop_modes_v3_battle;
uint8_t allowed_drop_modes_v3_challenge;
uint8_t allowed_drop_modes_v4_normal;
uint8_t allowed_drop_modes_v4_battle;
uint8_t allowed_drop_modes_v4_challenge;
Lobby::DropMode default_drop_mode_v1_v2_normal;
Lobby::DropMode default_drop_mode_v1_v2_battle;
Lobby::DropMode default_drop_mode_v1_v2_challenge;
Lobby::DropMode default_drop_mode_v3_normal;
Lobby::DropMode default_drop_mode_v3_battle;
Lobby::DropMode default_drop_mode_v3_challenge;
Lobby::DropMode default_drop_mode_v4_normal;
Lobby::DropMode default_drop_mode_v4_battle;
Lobby::DropMode default_drop_mode_v4_challenge;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;
bool use_temp_licenses_for_prototypes = true;
bool allow_dc_pc_games = true;
bool allow_gc_xb_games = true;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
uint8_t allowed_drop_modes_v1_v2_challenge = 0x07;
uint8_t allowed_drop_modes_v3_normal = 0x1F;
uint8_t allowed_drop_modes_v3_battle = 0x07;
uint8_t allowed_drop_modes_v3_challenge = 0x07;
uint8_t allowed_drop_modes_v4_normal = 0x1D; // CLIENT not allowed
uint8_t allowed_drop_modes_v4_battle = 0x05;
uint8_t allowed_drop_modes_v4_challenge = 0x05;
Lobby::DropMode default_drop_mode_v1_v2_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v1_v2_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_normal = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_battle = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v3_challenge = Lobby::DropMode::CLIENT;
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
QuestFlagsForDifficulty quest_flag_persist_mask;
uint64_t persistent_game_idle_timeout_usecs;
bool ep3_send_function_call_enabled;
bool catch_handler_exceptions;
bool ep3_infinite_meseta;
std::vector<uint32_t> ep3_defeat_player_meseta_rewards;
std::vector<uint32_t> ep3_defeat_com_meseta_rewards;
uint32_t ep3_final_round_meseta_bonus;
bool ep3_jukebox_is_free;
uint32_t ep3_behavior_flags;
bool hide_download_commands;
RunShellBehavior run_shell_behavior;
BehaviorSwitch cheat_mode_behavior;
uint64_t persistent_game_idle_timeout_usecs = 0;
bool ep3_send_function_call_enabled = false;
bool catch_handler_exceptions = true;
bool ep3_infinite_meseta = false;
std::vector<uint32_t> ep3_defeat_player_meseta_rewards = {400, 500, 600, 700, 800};
std::vector<uint32_t> ep3_defeat_com_meseta_rewards = {100, 200, 300, 400, 500};
uint32_t ep3_final_round_meseta_bonus = 300;
bool ep3_jukebox_is_free = false;
uint32_t ep3_behavior_flags = 0;
bool hide_download_commands = true;
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
@@ -161,21 +166,19 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results;
std::vector<ItemData> secret_lottery_results;
uint16_t bb_global_exp_multiplier;
uint16_t bb_global_exp_multiplier = 1;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
uint16_t ep3_card_auction_points;
uint16_t ep3_card_auction_min_size;
uint16_t ep3_card_auction_max_size;
uint16_t ep3_card_auction_points = 0;
uint16_t ep3_card_auction_min_size = 0;
uint16_t ep3_card_auction_max_size = 0;
struct CardAuctionPoolEntry {
uint64_t probability;
uint16_t card_id;
uint16_t min_price;
std::string card_name;
};
std::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::vector<std::vector<std::string>> ep3_trap_card_names;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry {
uint32_t type = 1;
@@ -212,26 +215,28 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
std::shared_ptr<struct event> destroy_lobbies_event;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order;
std::atomic<int32_t> next_lobby_id;
uint8_t pre_lobby_event;
int32_t ep3_menu_song;
std::atomic<int32_t> next_lobby_id = 1;
uint8_t pre_lobby_event = 0;
int32_t ep3_menu_song = -1;
std::map<std::string, uint32_t> all_addresses;
uint32_t local_address;
uint32_t external_address;
uint32_t local_address = 0;
uint32_t external_address = 0;
bool proxy_allow_save_files;
bool proxy_enable_login_options;
bool proxy_allow_save_files = true;
bool proxy_enable_login_options = false;
std::shared_ptr<ProxyServer> proxy_server;
std::shared_ptr<Server> game_server;
ServerState();
ServerState(std::shared_ptr<struct event_base> base, const std::string& config_filename, bool is_replay);
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
void init();
void load_objects(const std::string& what);
void load_objects(const std::vector<std::string>& what);
void add_client_to_available_lobby(std::shared_ptr<Client> c);
void remove_client_from_lobby(std::shared_ptr<Client> c);
@@ -278,21 +283,24 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
const std::string& gsl_filename = "",
const std::string& bb_directory_filename = "") const;
JSON load_config() const;
void create_load_step_graph();
void create_default_lobbies();
void collect_network_addresses();
void parse_config(const JSON& config_json, bool is_reload);
void load_config();
void load_bb_private_keys();
void load_licenses();
void load_teams();
void load_patch_indexes();
void load_battle_params();
void load_level_table();
void load_item_name_index();
void load_item_tables();
static std::shared_ptr<WordSelectTable> load_word_select_table_from_system();
void load_text_index();
static std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
Version version, std::shared_ptr<const ItemParameterTable> pmt, std::shared_ptr<const TextIndex> text_index);
void load_item_name_indexes();
void load_drop_tables();
void load_item_definitions();
void load_word_select_table();
void load_ep3_data();
void resolve_ep3_card_names();
void load_quest_index();
void compile_functions();
void load_dol_files();
+71
View File
@@ -0,0 +1,71 @@
#include "StepGraph.hh"
using namespace std;
void StepGraph::add_step(const string& name, const vector<string>& depends_on_names, function<void()>&& execute) {
auto new_step = make_shared<Step>(Step{.execute = std::move(execute)});
this->steps.emplace(name, new_step);
for (const auto& depends_on_name : depends_on_names) {
auto upstream_step = this->steps.at(depends_on_name);
upstream_step->downstream_dependencies.emplace_back(new_step);
}
}
void StepGraph::run(const string& start_step_name) {
vector<string> start_step_names({start_step_name});
this->run(start_step_names);
}
void StepGraph::run(const vector<string>& start_step_names) {
// Collect all steps to run
deque<shared_ptr<Step>> steps_to_visit;
try {
for (const auto& start_step_name : start_step_names) {
steps_to_visit.emplace_back(this->steps.at(start_step_name));
}
} catch (const out_of_range&) {
throw runtime_error("invalid step name");
}
unordered_set<shared_ptr<Step>> steps_to_run;
while (!steps_to_visit.empty()) {
auto step = std::move(steps_to_visit.front());
steps_to_visit.pop_front();
if (steps_to_run.emplace(step).second) {
for (const auto& downstream_step : step->downstream_dependencies) {
steps_to_visit.emplace_back(downstream_step);
}
}
}
// Topological sort: repeatedly take all steps that are not a downstream
// dependency of any other step in the set
vector<shared_ptr<Step>> steps_order;
steps_order.reserve(steps_to_run.size());
while (!steps_to_run.empty()) {
unordered_set<shared_ptr<Step>> candidate_steps = steps_to_run;
for (const auto& step : steps_to_run) {
for (const auto& downstream_step : step->downstream_dependencies) {
candidate_steps.erase(downstream_step);
}
}
if (candidate_steps.empty()) {
throw logic_error("dependency graph contains a cycle");
}
for (const auto& step : candidate_steps) {
steps_to_run.erase(step);
steps_order.emplace_back(step);
}
}
// Run the steps in order
uint64_t run_id = ++this->last_run_id;
for (auto step : steps_order) {
if (step->last_run_id < run_id) {
step->last_run_id = run_id;
if (step->execute) {
step->execute();
}
}
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <phosg/Strings.hh>
#include <string>
#include <unordered_map>
#include <vector>
struct StepGraph {
struct Step {
std::vector<std::shared_ptr<Step>> downstream_dependencies;
std::function<void()> execute;
uint64_t last_run_id = 0;
};
std::unordered_map<std::string, std::shared_ptr<Step>> steps;
uint64_t last_run_id = 0;
StepGraph() = default;
void add_step(const std::string& name, const std::vector<std::string>& depends_on_names, std::function<void()>&& execute);
void run(const std::string& start_step);
void run(const std::vector<std::string>& start_steps);
};