make reloading more stable and add more options

This commit is contained in:
Martin Michelsen
2023-03-15 14:47:06 -07:00
parent 45cac5a084
commit 7426c5ad1f
6 changed files with 411 additions and 336 deletions
+2 -311
View File
@@ -37,20 +37,6 @@ bool use_terminal_colors = false;
static vector<PortConfiguration> parse_port_configuration(
shared_ptr<const JSONObject> json) {
vector<PortConfiguration> ret;
for (const auto& item_json_it : json->as_dict()) {
auto item_list = item_json_it.second->as_list();
PortConfiguration& pc = ret.emplace_back();
pc.name = item_json_it.first;
pc.port = item_list[0]->as_int();
pc.version = version_for_name(item_list[1]->as_string().c_str());
pc.behavior = server_behavior_for_name(item_list[2]->as_string().c_str());
}
return ret;
}
template <typename T>
vector<T> parse_int_vector(shared_ptr<const JSONObject> o) {
vector<T> ret;
@@ -62,174 +48,6 @@ vector<T> parse_int_vector(shared_ptr<const JSONObject> o) {
void populate_state_from_config(shared_ptr<ServerState> s,
shared_ptr<JSONObject> config_json) {
const auto& d = config_json->as_dict();
s->name = decode_sjis(d.at("ServerName")->as_string());
try {
s->username = d.at("User")->as_string();
if (s->username == "$SUDO_USER") {
const char* user_from_env = getenv("SUDO_USER");
if (!user_from_env) {
throw runtime_error("configuration specifies $SUDO_USER, but variable is not defined");
}
s->username = user_from_env;
}
} catch (const out_of_range&) { }
s->set_port_configuration(parse_port_configuration(d.at("PortConfiguration")));
auto local_address_str = d.at("LocalAddress")->as_string();
try {
s->local_address = s->all_addresses.at(local_address_str);
string addr_str = string_for_address(s->local_address);
config_log.info("Added local address: %s (%s)", addr_str.c_str(),
local_address_str.c_str());
} catch (const out_of_range&) {
s->local_address = address_for_string(local_address_str.c_str());
config_log.info("Added local address: %s", local_address_str.c_str());
}
s->all_addresses.emplace("<local>", s->local_address);
auto external_address_str = d.at("ExternalAddress")->as_string();
try {
s->external_address = s->all_addresses.at(external_address_str);
string addr_str = string_for_address(s->external_address);
config_log.info("Added external address: %s (%s)", addr_str.c_str(),
external_address_str.c_str());
} catch (const out_of_range&) {
s->external_address = address_for_string(external_address_str.c_str());
config_log.info("Added external address: %s", external_address_str.c_str());
}
s->all_addresses.emplace("<external>", s->external_address);
try {
s->dns_server_port = d.at("DNSServerPort")->as_int();
} catch (const out_of_range&) {
s->dns_server_port = 0;
}
try {
for (const auto& item : d.at("IPStackListen")->as_list()) {
s->ip_stack_addresses.emplace_back(item->as_string());
}
} catch (const out_of_range&) { }
try {
s->ip_stack_debug = d.at("IPStackDebug")->as_bool();
} catch (const out_of_range&) { }
try {
s->allow_unregistered_users = d.at("AllowUnregisteredUsers")->as_bool();
} catch (const out_of_range&) {
s->allow_unregistered_users = true;
}
try {
s->item_tracking_enabled = d.at("EnableItemTracking")->as_bool();
} catch (const out_of_range&) {
s->item_tracking_enabled = true;
}
try {
s->episode_3_send_function_call_enabled = d.at("EnableEpisode3SendFunctionCall")->as_bool();
} catch (const out_of_range&) {
s->episode_3_send_function_call_enabled = false;
}
try {
s->catch_handler_exceptions = d.at("CatchHandlerExceptions")->as_bool();
} catch (const out_of_range&) {
s->catch_handler_exceptions = true;
}
try {
s->proxy_allow_save_files = d.at("ProxyAllowSaveFiles")->as_bool();
} catch (const out_of_range&) {
s->proxy_allow_save_files = true;
}
try {
s->proxy_enable_login_options = d.at("ProxyEnableLoginOptions")->as_bool();
} catch (const out_of_range&) {
s->proxy_enable_login_options = false;
}
try {
s->ep3_behavior_flags = d.at("Episode3BehaviorFlags")->as_int();
} catch (const out_of_range&) {
s->ep3_behavior_flags = 0;
}
try {
s->ep3_card_auction_points = d.at("CardAuctionPoints")->as_int();
} catch (const out_of_range&) {
s->ep3_card_auction_points = 0;
}
try {
auto i = d.at("CardAuctionSize");
if (i->is_int()) {
s->ep3_card_auction_min_size = i->as_int();
s->ep3_card_auction_max_size = s->ep3_card_auction_min_size;
} else {
s->ep3_card_auction_min_size = i->as_list().at(0)->as_int();
s->ep3_card_auction_max_size = i->as_list().at(1)->as_int();
}
} catch (const out_of_range&) {
s->ep3_card_auction_min_size = 0;
s->ep3_card_auction_max_size = 0;
}
try {
for (const auto& it : d.at("CardAuctionPool")->as_dict()) {
const auto& card_name = it.first;
const auto& card_cfg_json = it.second->as_list();
s->ep3_card_auction_pool.emplace(card_name, make_pair(
card_cfg_json.at(0)->as_int(), card_cfg_json.at(1)->as_int()));
}
} catch (const out_of_range&) { }
shared_ptr<JSONObject> log_levels_json;
try {
log_levels_json = d.at("LogLevels");
} catch (const out_of_range&) { }
if (log_levels_json.get()) {
set_log_levels_from_json(log_levels_json);
}
for (const string& filename : list_directory("system/blueburst/keys")) {
if (!ends_with(filename, ".nsk")) {
continue;
}
s->bb_private_keys.emplace_back(new PSOBBEncryption::KeyFile(
load_object_file<PSOBBEncryption::KeyFile>("system/blueburst/keys/" + filename)));
config_log.info("Loaded Blue Burst key file: %s", filename.c_str());
}
config_log.info("%zu Blue Burst key file(s) loaded", s->bb_private_keys.size());
try {
bool run_shell = d.at("RunInteractiveShell")->as_bool();
s->run_shell_behavior = run_shell ?
ServerState::RunShellBehavior::ALWAYS :
ServerState::RunShellBehavior::NEVER;
} catch (const out_of_range&) { }
try {
auto v = d.at("LobbyEvent");
uint8_t event = v->is_int() ? v->as_int() : event_for_name(v->as_string());
s->pre_lobby_event = event;
for (const auto& l : s->all_lobbies()) {
l->event = event;
}
} catch (const out_of_range&) { }
try {
s->ep3_menu_song = d.at("Episode3MenuSong")->as_int();
} catch (const out_of_range&) { }
}
void drop_privileges(const string& username) {
if ((getuid() != 0) || (getgid() != 0)) {
throw runtime_error(string_printf(
@@ -1022,6 +840,7 @@ int main(int argc, char** argv) {
case Behavior::REPLAY_LOG:
case Behavior::RUN_SERVER: {
bool is_replay = behavior == Behavior::REPLAY_LOG;
signal(SIGPIPE, SIG_IGN);
if (isatty(fileno(stderr))) {
@@ -1029,135 +848,7 @@ int main(int argc, char** argv) {
}
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
shared_ptr<ServerState> state(new ServerState());
config_log.info("Reading network addresses");
state->all_addresses = get_local_addresses();
for (const auto& it : state->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());
}
config_log.info("Loading configuration");
auto config_json = JSONObject::parse(load_file(config_filename));
populate_state_from_config(state, config_json);
if (behavior != Behavior::REPLAY_LOG) {
config_log.info("Loading license list");
state->license_manager.reset(new LicenseManager("system/licenses.nsi"));
} else {
state->license_manager.reset(new LicenseManager());
}
if (isdir("system/patch-pc")) {
config_log.info("Indexing PSO PC patch files");
state->pc_patch_file_index.reset(new PatchFileIndex("system/patch-pc"));
} else {
config_log.info("PSO PC patch files not present");
}
if (isdir("system/patch-bb")) {
config_log.info("Indexing PSO BB patch files");
state->bb_patch_file_index.reset(new PatchFileIndex("system/patch-bb"));
try {
auto gsl_file = state->bb_patch_file_index->get("./data/data.gsl");
state->bb_data_gsl.reset(new GSLArchive(gsl_file->load_data(), false));
config_log.info("data.gsl found in BB patch files");
} catch (const out_of_range&) {
config_log.info("data.gsl is not present in BB patch files");
}
} else {
config_log.info("PSO BB patch files not present");
}
config_log.info("Loading battle parameters");
state->battle_params.reset(new BattleParamsIndex(
state->load_bb_file("BattleParamEntry_on.dat"),
state->load_bb_file("BattleParamEntry_lab_on.dat"),
state->load_bb_file("BattleParamEntry_ep4_on.dat"),
state->load_bb_file("BattleParamEntry.dat"),
state->load_bb_file("BattleParamEntry_lab.dat"),
state->load_bb_file("BattleParamEntry_ep4.dat")));
config_log.info("Loading level table");
state->level_table.reset(new LevelTable(
state->load_bb_file("PlyLevelTbl.prs"), true));
config_log.info("Loading rare item table");
state->rare_item_set.reset(new RELRareItemSet(
state->load_bb_file("ItemRT.rel")));
// Note: These files don't exist in BB, so we use the GC versions of them
// instead. This doesn't include Episode 4 of course, so we use Episode 1
// parameters for Episode 4 implicitly.
{
config_log.info("Loading common item tables");
shared_ptr<string> pt_data(new string(load_file(
"system/blueburst/ItemPT_GC.gsl")));
state->common_item_set.reset(new CommonItemSet(pt_data));
shared_ptr<string> armor_data(new string(load_file(
"system/blueburst/ArmorRandom_GC.rel")));
state->armor_random_set.reset(new ArmorRandomSet(armor_data));
shared_ptr<string> tool_data(new string(load_file(
"system/blueburst/ToolRandom_GC.rel")));
state->tool_random_set.reset(new ToolRandomSet(tool_data));
const char* filenames[4] = {
"system/blueburst/WeaponRandomNormal_GC.rel",
"system/blueburst/WeaponRandomHard_GC.rel",
"system/blueburst/WeaponRandomVeryHard_GC.rel",
"system/blueburst/WeaponRandomUltimate_GC.rel",
};
for (size_t z = 0; z < 4; z++) {
shared_ptr<string> weapon_data(new string(load_file(filenames[z])));
state->weapon_random_sets[z].reset(new WeaponRandomSet(weapon_data));
}
}
config_log.info("Loading item definition table");
{
shared_ptr<string> data(new string(prs_decompress(load_file(
"system/blueburst/ItemPMT.prs"))));
state->item_parameter_table.reset(new ItemParameterTable(data));
}
config_log.info("Collecting Episode 3 data");
state->ep3_data_index.reset(new Episode3::DataIndex(
"system/ep3", state->ep3_behavior_flags));
const string& tournament_state_filename = "system/ep3/tournament-state.json";
try {
state->ep3_tournament_index.reset(new Episode3::TournamentIndex(
state->ep3_data_index, tournament_state_filename));
config_log.info("Loaded Episode 3 tournament state");
} catch (const exception& e) {
config_log.warning("Cannot load Episode 3 tournament state: %s", e.what());
state->ep3_tournament_index.reset(new Episode3::TournamentIndex(
state->ep3_data_index, tournament_state_filename, true));
}
config_log.info("Collecting quest metadata");
state->quest_index.reset(new QuestIndex("system/quests"));
if (behavior != Behavior::REPLAY_LOG) {
config_log.info("Compiling client functions");
state->function_code_index.reset(new FunctionCodeIndex("system/ppc"));
config_log.info("Loading DOL files");
state->dol_file_index.reset(new DOLFileIndex("system/dol"));
} else {
state->function_code_index.reset(new FunctionCodeIndex());
state->dol_file_index.reset(new DOLFileIndex());
}
config_log.info("Creating menus");
state->create_menus(config_json);
if (behavior == Behavior::REPLAY_LOG) {
state->allow_saving = false;
state->license_manager->set_autosave(false);
config_log.info("Saving disabled because this is a replay session");
}
shared_ptr<ServerState> state(new ServerState(config_filename, is_replay));
shared_ptr<DNSServer> dns_server;
if (state->dns_server_port && (behavior != Behavior::REPLAY_LOG)) {