make replay tests run in parallel and share immutable data

This commit is contained in:
Martin Michelsen
2026-06-14 09:24:56 -07:00
parent 1737d8abc8
commit 629e2bb4cd
28 changed files with 3357 additions and 3188 deletions
+6 -11
View File
@@ -67,6 +67,7 @@ set(SOURCES
src/DCSerialNumbers.cc
src/DNSServer.cc
src/DOLFileIndex.cc
src/DataIndex.cc
src/DownloadSession.cc
src/EnemyType.cc
src/Episode3/AssistServer.cc
@@ -157,17 +158,11 @@ add_dependencies(newserv newserv-Revision-cc)
enable_testing()
file(GLOB LOG_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.txt)
foreach(LOG_TEST_CASE IN ITEMS ${LOG_TEST_CASES})
add_test(
NAME ${LOG_TEST_CASE}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LOG_TEST_CASE} --config=${CMAKE_SOURCE_DIR}/tests/config.json)
endforeach()
# list(TRANSFORM LOG_TEST_CASES PREPEND "--replay-log=" OUTPUT_VARIABLE LOG_REPLAY_ARGS)
# add_test(
# NAME "log-replays"
# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
# COMMAND ${CMAKE_BINARY_DIR}/newserv --config=${CMAKE_SOURCE_DIR}/tests/config.json ${LOG_REPLAY_ARGS})
list(TRANSFORM LOG_TEST_CASES PREPEND "--replay-log=" OUTPUT_VARIABLE LOG_REPLAY_ARGS)
add_test(
NAME "log-replays"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_BINARY_DIR}/newserv --parallel --config=${CMAKE_SOURCE_DIR}/tests/config.json ${LOG_REPLAY_ARGS})
file(GLOB SCRIPT_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
foreach(SCRIPT_TEST_CASE IN ITEMS ${SCRIPT_TEST_CASES})
+62 -61
View File
@@ -142,7 +142,7 @@ struct Args {
void check_cheat_mode_available(bool behavior_is_cheating) const {
if (behavior_is_cheating &&
this->check_permissions &&
(this->c->require_server_state()->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) &&
(this->c->require_server_state()->data->cheat_mode_behavior == DataIndex::BehaviorSwitch::OFF) &&
(!this->c->login || !this->c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))) {
throw precondition_failed("$C6Cheats are disabled");
}
@@ -219,7 +219,7 @@ static asio::awaitable<void> server_command_announce_inner(const Args& a, bool m
auto s = a.c->require_server_state();
if (anonymous) {
if (mail) {
send_simple_mail(s, 0, s->name, a.text);
send_simple_mail(s, 0, s->data->name, a.text);
} else {
send_text_or_scrolling_message(s, a.text, a.text);
}
@@ -472,8 +472,8 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
} else {
dest_character_index = stoull(a.text) - 1;
if (dest_character_index >= s->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
if (dest_character_index >= s->data->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->data->num_backup_character_slots);
}
dest_account = a.c->login->account;
}
@@ -514,13 +514,13 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
// Client sent 61; generate a BB-format player from the information we have and save that instead
if (ch.character) {
auto bb_player = PSOBBCharacterFile::create_from_config(
a.c->login->account->account_id, a.c->language(), ch.character->disp.visual, s->level_table(a.c->version()));
a.c->login->account->account_id, a.c->language(), ch.character->disp.visual, s->data->level_table(a.c->version()));
bb_player->disp.visual.sh.version = 4;
bb_player->disp.visual.sh.name_color_checksum = 0x00000000;
bb_player->inventory = ch.character->inventory;
// Before V3, player stats can't be correctly computed from other fields because material usage isn't stored
// anywhere. For these versions, we have to trust the stats field from the player's data.
auto level_table = s->level_table(a.c->version());
auto level_table = s->data->level_table(a.c->version());
if (is_v1_or_v2(a.c->version())) {
bb_player->disp.stats = ch.character->disp.stats;
bb_player->import_tethealla_material_usage(level_table);
@@ -578,8 +578,8 @@ ChatCommandDefinition cc_cheat(
auto s = a.c->require_server_state();
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
!a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE) &&
s->cheat_flags.insufficient_minimum_level) {
size_t default_min_level = s->default_min_level_for_game(a.c->version(), l->episode, l->difficulty);
s->data->cheat_flags.insufficient_minimum_level) {
size_t default_min_level = s->data->default_min_level_for_game(a.c->version(), l->episode, l->difficulty);
if (l->min_level < default_min_level) {
l->min_level = default_min_level;
send_text_message_fmt(l, "$C6Minimum level set\nto {}", l->min_level + 1);
@@ -603,7 +603,7 @@ ChatCommandDefinition cc_checkchar(
std::vector<bool> flags;
flags.emplace_back(false);
for (size_t z = 0; z < s->num_backup_character_slots; z++) {
for (size_t z = 0; z < s->data->num_backup_character_slots; z++) {
std::string filename = a.c->backup_character_filename(a.c->login->account->account_id, z, is_ep3);
flags.emplace_back(std::filesystem::is_regular_file(filename));
}
@@ -615,8 +615,8 @@ ChatCommandDefinition cc_checkchar(
} else {
size_t index = stoull(a.text, nullptr, 0) - 1;
if (index >= s->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
if (index >= s->data->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->data->num_backup_character_slots);
}
try {
@@ -760,7 +760,7 @@ ChatCommandDefinition cc_dropmode(
+[](const Args& a) -> asio::awaitable<void> {
a.check_is_game(true);
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.proxy_override_drops);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.proxy_override_drops);
if (a.c->proxy_session) {
@@ -881,7 +881,7 @@ ChatCommandDefinition cc_edit(
}
bool cheats_allowed = (!a.check_permissions ||
(s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) ||
(s->data->cheat_mode_behavior != DataIndex::BehaviorSwitch::OFF) ||
a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
std::string encoded_args = phosg::tolower(a.text);
@@ -891,28 +891,28 @@ ChatCommandDefinition cc_edit(
try {
auto p = a.c->character_file();
if (tokens.at(0) == "atp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
if (tokens.at(0) == "atp" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.atp = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "mst" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "mst" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.mst = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "evp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "evp" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.evp = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "hp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "hp" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.hp = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "dfp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "dfp" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.dfp = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "ata" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "ata" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.ata = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "lck" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "lck" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.lck = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.meseta = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.exp = std::stoul(tokens.at(1));
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
p->disp.stats.level = std::stoul(tokens.at(1)) - 1;
p->recompute_stats(s->level_table(a.c->version()), true);
} else if (((tokens.at(0) == "material") || (tokens.at(0) == "mat")) && !is_v1_or_v2(a.c->version()) && (cheats_allowed || !s->cheat_flags.reset_materials)) {
p->recompute_stats(s->data->level_table(a.c->version()), true);
} else if (((tokens.at(0) == "material") || (tokens.at(0) == "mat")) && !is_v1_or_v2(a.c->version()) && (cheats_allowed || !s->data->cheat_flags.reset_materials)) {
if (tokens.at(1) == "reset") {
const auto& which = tokens.at(2);
if (which == "power") {
@@ -949,7 +949,7 @@ ChatCommandDefinition cc_edit(
} else {
throw precondition_failed("$C6Invalid subcommand");
}
p->recompute_stats(s->level_table(a.c->version()), false);
p->recompute_stats(s->data->level_table(a.c->version()), false);
} else if (tokens.at(0) == "namecolor") {
p->disp.visual.sh.name_color = std::stoul(tokens.at(1), nullptr, 16);
} else if (tokens.at(0) == "language" || tokens.at(0) == "lang") {
@@ -965,7 +965,7 @@ ChatCommandDefinition cc_edit(
sys->language = new_language;
}
} else if (tokens.at(0) == "secid") {
if (!cheats_allowed && (p->disp.stats.level > 0) && s->cheat_flags.edit_section_id) {
if (!cheats_allowed && (p->disp.stats.level > 0) && s->data->cheat_flags.edit_section_id) {
throw precondition_failed("$C6You cannot change\nyour Section ID\nafter level 1");
}
uint8_t secid = section_id_for_name(tokens.at(1));
@@ -991,7 +991,7 @@ ChatCommandDefinition cc_edit(
p->disp.visual.sh.extra_model = npc;
p->disp.visual.sh.validation_flags |= 0x02;
}
} else if (tokens.at(0) == "tech" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
} else if (tokens.at(0) == "tech" && (cheats_allowed || !s->data->cheat_flags.edit_stats)) {
uint8_t level = std::stoul(tokens.at(2)) - 1;
if (tokens.at(1) == "all") {
for (size_t x = 0; x < 0x14; x++) {
@@ -1106,7 +1106,7 @@ ChatCommandDefinition cc_exit(
auto s = a.c->require_server_state();
std::shared_ptr<const ClientFunctionIndex::Function> fn;
try {
fn = s->client_functions->get("ExitAnywhere", a.c->specific_version);
fn = s->data->client_functions->get("ExitAnywhere", a.c->specific_version);
} catch (const std::out_of_range&) {
}
if (fn) {
@@ -1154,7 +1154,7 @@ ChatCommandDefinition cc_infhp(
send_text_message(a.c, "$C6Infinite HP disabled");
} else {
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.infinite_hp_tp);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.infinite_hp_tp);
a.c->set_flag(Client::Flag::INFINITE_HP_ENABLED);
co_await send_remove_negative_conditions(a.c);
if (a.c->proxy_session) {
@@ -1202,7 +1202,7 @@ ChatCommandDefinition cc_inftp(
send_text_message(a.c, "$C6Infinite TP disabled");
} else {
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.infinite_hp_tp);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.infinite_hp_tp);
a.c->set_flag(Client::Flag::INFINITE_TP_ENABLED);
send_text_message(a.c, "$C6Infinite TP enabled");
}
@@ -1214,7 +1214,7 @@ ChatCommandDefinition cc_item(
+[](const Args& a) -> asio::awaitable<void> {
a.check_is_game(true);
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.create_items);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.create_items);
ItemData item;
bool was_enqueued = false;
@@ -1225,12 +1225,12 @@ ChatCommandDefinition cc_item(
a.check_is_leader();
if (a.text.starts_with("!")) {
item = s->parse_item_description(a.c->version(), a.text.substr(1));
item = s->data->parse_item_description(a.c->version(), a.text.substr(1));
a.c->proxy_session->next_drop_item = item;
was_enqueued = true;
} else {
item = s->parse_item_description(a.c->version(), a.text);
item = s->data->parse_item_description(a.c->version(), a.text);
item.id = phosg::random_object<uint32_t>() | 0x80000000;
send_drop_stacked_item_to_channel(s, a.c->channel, item, a.c->floor, a.c->pos);
@@ -1239,7 +1239,7 @@ ChatCommandDefinition cc_item(
} else {
auto l = a.c->require_lobby();
item = s->parse_item_description(a.c->version(), a.text);
item = s->data->parse_item_description(a.c->version(), a.text);
item.id = l->generate_item_id(0xFF);
if ((l->drop_mode == ServerDropMode::SERVER_PRIVATE) || (l->drop_mode == ServerDropMode::SERVER_DUPLICATE)) {
@@ -1251,7 +1251,7 @@ ChatCommandDefinition cc_item(
}
}
std::string name = s->describe_item(a.c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
std::string name = s->data->describe_item(a.c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
if (was_enqueued) {
send_text_message(a.c, "$C7Next item:\n" + name);
} else {
@@ -1340,7 +1340,7 @@ ChatCommandDefinition cc_killcount(
auto s = a.c->require_server_state();
for (size_t z : item_indexes) {
const auto& item = p->inventory.items[z];
std::string name = s->describe_item(
std::string name = s->data->describe_item(
a.c->version(), item.data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES | ItemNameIndex::Flag::NAME_ONLY);
send_text_message_fmt(a.c, "{}$C7: {} kills", name, item.data.get_kill_count());
}
@@ -1530,8 +1530,8 @@ ChatCommandDefinition cc_loadchar(
auto l = a.c->require_lobby();
size_t index = stoull(a.text, nullptr, 0) - 1;
if (index >= s->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
if (index >= s->data->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->data->num_backup_character_slots);
}
std::shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
@@ -1563,7 +1563,7 @@ ChatCommandDefinition cc_loadchar(
auto send_set_extended_player_info = [&a, &s]<typename CharT>(const CharT& char_file) -> asio::awaitable<void> {
co_await prepare_client_for_patches(a.c);
try {
auto fn = s->client_functions->get("SetExtendedPlayerInfo", a.c->specific_version);
auto fn = s->data->client_functions->get("SetExtendedPlayerInfo", a.c->specific_version);
co_await send_function_call(a.c, fn, {}, &char_file, sizeof(CharT));
auto l = a.c->lobby.lock();
if (l) {
@@ -1699,7 +1699,7 @@ ChatCommandDefinition cc_makeobj(
co_await prepare_client_for_patches(a.c);
auto s = a.c->require_server_state();
auto fn = s->client_functions->get("CreateObject", a.c->specific_version);
auto fn = s->data->client_functions->get("CreateObject", a.c->specific_version);
co_await send_function_call(a.c, fn, label_writes);
});
@@ -1760,8 +1760,8 @@ ChatCommandDefinition cc_minlevel(
auto s = a.c->require_server_state();
bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) ||
a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
if (!cheats_allowed && s->cheat_flags.insufficient_minimum_level) {
size_t default_min_level = s->default_min_level_for_game(a.c->version(), l->episode, l->difficulty);
if (!cheats_allowed && s->data->cheat_flags.insufficient_minimum_level) {
size_t default_min_level = s->data->default_min_level_for_game(a.c->version(), l->episode, l->difficulty);
if (new_min_level < default_min_level) {
throw precondition_failed("$C6Cannot set minimum\nlevel below {}", default_min_level + 1);
}
@@ -1777,7 +1777,7 @@ ChatCommandDefinition cc_next(
+[](const Args& a) -> asio::awaitable<void> {
a.check_is_game(true);
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.warp);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.warp);
auto episode = a.c->proxy_session ? a.c->proxy_session->lobby_episode : a.c->require_lobby()->episode;
size_t limit = FloorDefinition::limit_for_episode(episode);
@@ -1838,7 +1838,7 @@ ChatCommandDefinition cc_patch(
try {
auto s = a.c->require_server_state();
// Note: We can't look this up before prepare_client_for_patches because specific_version may not be set
auto fn = s->client_functions->get(patch_name, a.c->specific_version);
auto fn = s->data->client_functions->get(patch_name, a.c->specific_version);
switch (fn->visibility) {
case ClientFunctionIndex::Function::Visibility::DEBUG_ONLY:
@@ -2060,7 +2060,7 @@ ChatCommandDefinition cc_qfread(
uint8_t counter_index;
uint32_t mask;
try {
const auto& def = s->quest_counter_fields.at(a.text);
const auto& def = s->data->quest_counter_fields.at(a.text);
counter_index = def.first;
mask = def.second;
} catch (const std::out_of_range&) {
@@ -2189,7 +2189,7 @@ ChatCommandDefinition cc_quest(
a.check_is_game(true);
auto s = a.c->require_server_state();
auto q = s->quest_index->get(stoul(a.text));
auto q = s->data->quest_index->get(stoul(a.text));
if (!q) {
throw precondition_failed("$C6Quest not found");
}
@@ -2229,7 +2229,7 @@ ChatCommandDefinition cc_fastkill(
send_text_message(a.c, "$C6Fast kills disabled");
} else {
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.fast_kills);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.fast_kills);
a.c->set_flag(Client::Flag::FAST_KILLS_ENABLED);
send_text_message(a.c, "$C6Fast kills enabled");
}
@@ -2251,7 +2251,7 @@ ChatCommandDefinition cc_rand(
auto s = a.c->require_server_state();
auto l = a.c->require_lobby();
a.check_is_game(false);
a.check_cheats_enabled_or_allowed(s->cheat_flags.override_random_seed);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.override_random_seed);
if (a.text.empty()) {
a.c->override_random_seed = -1;
@@ -2288,7 +2288,7 @@ ChatCommandDefinition cc_readmem(
std::shared_ptr<const ClientFunctionIndex::Function> fn;
try {
auto s = a.c->require_server_state();
fn = s->client_functions->get("ReadMemoryWord", a.c->specific_version);
fn = s->data->client_functions->get("ReadMemoryWord", a.c->specific_version);
} catch (const std::out_of_range&) {
throw precondition_failed("Invalid patch name");
}
@@ -2332,7 +2332,7 @@ ChatCommandDefinition cc_savefiles(
a.check_is_proxy(true);
auto s = a.c->require_server_state();
if (!s->proxy_allow_save_files) {
if (!s->data->proxy_allow_save_files) {
send_text_message(a.c, "$C6Save files is not\nallowed");
} else if (a.c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
a.c->clear_flag(Client::Flag::PROXY_SAVE_FILES);
@@ -2406,7 +2406,7 @@ ChatCommandDefinition cc_secid(
{"$secid"},
+[](const Args& a) -> asio::awaitable<void> {
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.override_section_id);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.override_section_id);
uint8_t new_override_section_id;
if (a.text.empty()) {
@@ -2439,7 +2439,7 @@ ChatCommandDefinition cc_setassist(
a.check_is_game(true);
a.check_is_ep3(true);
auto s = a.c->require_server_state();
a.check_cheats_enabled_in_game(s->cheat_flags.ep3_replace_assist);
a.check_cheats_enabled_in_game(s->data->cheat_flags.ep3_replace_assist);
auto l = a.c->require_lobby();
if (l->episode != Episode::EP3) {
@@ -2486,7 +2486,7 @@ ChatCommandDefinition cc_server_info(
{"$si"},
+[](const Args& a) -> asio::awaitable<void> {
auto s = a.c->require_server_state();
std::string uptime_str = phosg::format_duration(phosg::now() - s->creation_time);
std::string uptime_str = phosg::format_duration(phosg::now() - s->data->creation_time);
send_text_message_fmt(a.c,
"Uptime: $C6{}$C7\nLobbies: $C6{}$C7\nClients: $C6{}$C7(g) $C6{}$C7(p)",
uptime_str,
@@ -2847,7 +2847,7 @@ ChatCommandDefinition cc_unset(
a.check_is_game(true);
a.check_is_ep3(true);
auto s = a.c->require_server_state();
a.check_cheats_enabled_in_game(s->cheat_flags.ep3_unset_field_character);
a.check_cheats_enabled_in_game(s->data->cheat_flags.ep3_unset_field_character);
auto l = a.c->require_lobby();
if (l->episode != Episode::EP3) {
throw std::logic_error("non-Ep3 client in Ep3 game");
@@ -2875,7 +2875,7 @@ ChatCommandDefinition cc_variations(
a.check_is_proxy(false);
a.check_is_game(false);
auto s = a.c->require_server_state();
a.check_cheats_enabled_in_game(s->cheat_flags.override_variations);
a.check_cheats_enabled_in_game(s->data->cheat_flags.override_variations);
a.c->override_variations = std::make_unique<Variations>();
for (size_t z = 0; z < std::min<size_t>(a.c->override_variations->entries.size() * 2, a.text.size()); z++) {
@@ -2894,7 +2894,7 @@ ChatCommandDefinition cc_variations(
static void command_warp(const Args& a, bool is_warpall) {
a.check_is_game(true);
auto s = a.c->require_server_state();
a.check_cheats_enabled_or_allowed(s->cheat_flags.warp);
a.check_cheats_enabled_or_allowed(s->data->cheat_flags.warp);
uint32_t floor = std::stoul(a.text, nullptr, 0);
if (!is_warpall && (a.c->floor == floor)) {
@@ -2968,7 +2968,8 @@ ChatCommandDefinition cc_what(
} else {
auto s = a.c->require_server_state();
send_text_message(
a.c, s->describe_item(a.c->version(), nearest_fi->data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES));
a.c,
s->data->describe_item(a.c->version(), nearest_fi->data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES));
}
co_return;
});
@@ -3002,7 +3003,7 @@ static void whatobj_whatene_fn(const Args& a, bool include_objs, bool include_en
VectorXYZF worldspace_pos;
if (l->episode != Episode::EP3) {
try {
const auto& room = s->room_layout_index->get_room(area, layout_var, def.set_entry->room);
const auto& room = s->data->room_layout_index->get_room(area, layout_var, def.set_entry->room);
// This is the order in which the game does the rotations; not sure why
worldspace_pos = def.set_entry->pos.rotate_x(room.angle.x).rotate_z(room.angle.z).rotate_y(room.angle.y) + room.position;
} catch (const std::out_of_range&) {
@@ -3141,7 +3142,7 @@ ChatCommandDefinition cc_writemem(
try {
auto s = a.c->require_server_state();
auto fn = s->client_functions->get("WriteMemory", a.c->specific_version);
auto fn = s->data->client_functions->get("WriteMemory", a.c->specific_version);
std::unordered_map<std::string, uint32_t> label_writes{{"dest_addr", addr}, {"size", data.size()}};
co_await send_function_call(a.c, fn, label_writes, data.data(), data.size());
} catch (const std::out_of_range&) {
@@ -3181,7 +3182,7 @@ ChatCommandDefinition cc_nativecall(
try {
auto s = a.c->require_server_state();
auto fn = s->client_functions->get("CallNativeFunction", a.c->specific_version);
auto fn = s->data->client_functions->get("CallNativeFunction", a.c->specific_version);
co_await send_function_call(a.c, fn, label_writes);
} catch (const std::out_of_range&) {
throw precondition_failed("Invalid patch name");
+19 -19
View File
@@ -191,7 +191,7 @@ Client::Client(
// Don't print data sent to patch clients to the logs. The patch server protocol is fully understood and data logs
// for patch clients are generally more annoying than helpful at this point.
auto s = server->get_state();
if (is_patch(this->version()) && s->hide_download_commands) {
if (is_patch(this->version()) && s->data->hide_download_commands) {
this->channel->terminal_recv_color = phosg::TerminalFormat::END;
this->channel->terminal_send_color = phosg::TerminalFormat::END;
} else {
@@ -200,7 +200,7 @@ Client::Client(
}
this->set_flags_for_version(this->version(), -1);
if (is_v1_or_v2(this->version()) ? s->default_rare_notifs_enabled_v1_v2 : s->default_rare_notifs_enabled_v3_v4) {
if (is_v1_or_v2(this->version()) ? s->data->default_rare_notifs_enabled_v1_v2 : s->data->default_rare_notifs_enabled_v3_v4) {
this->set_drop_notification_mode(ItemDropNotificationMode::RARES_ONLY);
}
this->specific_version = default_specific_version_for_version(this->version(), -1);
@@ -210,7 +210,7 @@ Client::Client(
// Don't print data sent to patch clients to the logs. The patch server protocol is fully understood and data logs
// for patch clients are generally more annoying than helpful at this point.
if ((s->hide_download_commands) &&
if ((s->data->hide_download_commands) &&
((this->version() == Version::PC_PATCH) || (this->version() == Version::BB_PATCH))) {
this->channel->terminal_recv_color = phosg::TerminalFormat::END;
this->channel->terminal_send_color = phosg::TerminalFormat::END;
@@ -267,7 +267,7 @@ void Client::reschedule_save_game_data_timer() {
void Client::reschedule_ping_and_timeout_timers() {
auto s = this->require_server_state();
if (!is_patch(this->version())) {
this->send_ping_timer.expires_after(std::chrono::microseconds(s->client_ping_interval_usecs));
this->send_ping_timer.expires_after(std::chrono::microseconds(s->data->client_ping_interval_usecs));
this->send_ping_timer.async_wait([this](std::error_code ec) {
if (!ec) {
this->log.info_f("Sending ping command");
@@ -282,7 +282,7 @@ void Client::reschedule_ping_and_timeout_timers() {
});
}
this->idle_timeout_timer.expires_after(std::chrono::microseconds(s->client_idle_timeout_usecs));
this->idle_timeout_timer.expires_after(std::chrono::microseconds(s->data->client_idle_timeout_usecs));
this->idle_timeout_timer.async_wait([this](std::error_code ec) {
if (!ec) {
this->log.info_f("Idle timeout expired");
@@ -295,7 +295,7 @@ void Client::convert_account_to_temporary_if_nte() {
// If the session is a prototype version and the account was created and we should use a temporary account instead,
// delete the permanent account and replace it with a temporary account.
auto s = this->require_server_state();
if (s->use_temp_accounts_for_prototypes && this->login->account_was_created && is_any_nte(this->version())) {
if (s->data->use_temp_accounts_for_prototypes && this->login->account_was_created && is_any_nte(this->version())) {
this->log.info_f("Client is a prototype version and the account was created during this session; converting permanent account to temporary account");
this->login->account->is_temporary = true;
this->login->account->delete_file();
@@ -431,14 +431,14 @@ bool Client::can_use_chat_commands() const {
if (this->login->account->check_flag(Account::Flag::ALWAYS_ENABLE_CHAT_COMMANDS)) {
return true;
}
return this->require_server_state()->enable_chat_commands;
return this->require_server_state()->data->enable_chat_commands;
}
void Client::set_login(std::shared_ptr<Login> login) {
this->login = login;
auto s = this->require_server_state();
if (!s->allow_same_account_concurrent_logins) {
if (!s->data->allow_same_account_concurrent_logins) {
auto it = s->client_for_account.find(login->account->account_id);
if ((it != s->client_for_account.end()) && (it->second.get() != this)) {
if (it->second->channel) {
@@ -796,8 +796,8 @@ std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
}
auto s = this->require_server_state();
this->bank_data->max_items = s->bb_max_bank_items;
this->bank_data->max_meseta = s->bb_max_bank_meseta;
this->bank_data->max_items = s->data->bb_max_bank_items;
this->bank_data->max_meseta = s->data->bb_max_bank_meseta;
this->update_bank_data_after_load(this->bank_data);
}
return this->bank_data;
@@ -1000,11 +1000,11 @@ void Client::load_all_files() {
if (!this->system_data) {
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
auto s = this->require_server_state();
if (s->bb_default_keyboard_config) {
this->system_data->key_config = *s->bb_default_keyboard_config;
if (s->data->bb_default_keyboard_config) {
this->system_data->key_config = *s->data->bb_default_keyboard_config;
}
if (s->bb_default_joystick_config) {
this->system_data->joystick_config = *s->bb_default_joystick_config;
if (s->data->bb_default_joystick_config) {
this->system_data->joystick_config = *s->data->bb_default_joystick_config;
}
this->log.info_f("Created new system data");
}
@@ -1014,7 +1014,7 @@ void Client::load_all_files() {
}
auto s = this->require_server_state();
auto stack_limits = s->item_stack_limits(this->version());
auto stack_limits = s->data->item_stack_limits(this->version());
this->blocked_senders.clear();
for (size_t z = 0; z < this->guild_card_data->blocked_senders.size(); z++) {
@@ -1039,7 +1039,7 @@ void Client::load_all_files() {
}
void Client::update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> charfile) {
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
charfile->import_tethealla_material_usage(this->require_server_state()->data->level_table(this->version()));
Language lang = this->language();
this->log.info_f("Overriding language fields in save files with {}", name_for_language(lang));
@@ -1049,7 +1049,7 @@ void Client::update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile
void Client::update_bank_data_after_load(std::shared_ptr<PlayerBank> bank) {
auto s = this->require_server_state();
auto limits = s->item_stack_limits(this->version());
auto limits = s->data->item_stack_limits(this->version());
for (auto& item : bank->items) {
if (item.data.is_stackable(*limits)) {
if (item.data.data1[5] != item.amount) {
@@ -1121,7 +1121,7 @@ void Client::print_inventory() const {
for (size_t x = 0; x < p->inventory.num_items; x++) {
const auto& item = p->inventory.items[x];
auto hex = item.data.hex();
auto name = s->describe_item(this->version(), item.data);
auto name = s->data->describe_item(this->version(), item.data);
this->log.info_f("[PlayerInventory] {:2}: [+{:08X}] {} ({})", x, item.flags, hex, name);
}
}
@@ -1135,7 +1135,7 @@ void Client::print_bank() const {
const auto& item = this->bank_data->items[x];
const char* present_token = item.present ? "" : " (missing present flag)";
auto hex = item.data.hex();
auto name = s->describe_item(this->version(), item.data);
auto name = s->data->describe_item(this->version(), item.data);
this->log.info_f("[PlayerBank] {:3}: {} ({}) (x{}){}", x, hex, name, item.amount, present_token);
}
} else {
+4 -5
View File
@@ -14,8 +14,7 @@
#include "NetworkAddresses.hh"
#include "ServerState.hh"
DNSServer::DNSServer(std::shared_ptr<ServerState> state)
: state(state) {}
DNSServer::DNSServer(std::shared_ptr<ServerState> state) : state(state) {}
void DNSServer::listen(const std::string& addr, int port) {
if (port == 0) {
@@ -62,11 +61,11 @@ asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::
if (bytes < 0x0C) {
dns_server_log.warning_f("input query too small");
phosg::print_data(stderr, input.data(), bytes);
} else if (!this->state->banned_ipv4_ranges->check(sender_addr)) {
} else if (!this->state->data->banned_ipv4_ranges->check(sender_addr)) {
input.resize(bytes);
uint32_t connect_address = is_local_address(sender_addr)
? this->state->local_address
: this->state->external_address;
? this->state->data->local_address
: this->state->data->external_address;
std::string response = this->response_for_query(input, connect_address);
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
}
+1 -1
View File
@@ -8,7 +8,7 @@
#include "IPV4RangeSet.hh"
struct ServerState;
class ServerState;
class DNSServer {
public:
+2037
View File
File diff suppressed because it is too large Load Diff
+412
View File
@@ -0,0 +1,412 @@
#pragma once
#include <atomic>
#include <map>
#include <memory>
#include <phosg/JSON.hh>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "Account.hh"
#include "Client.hh"
#include "ClientFunctionIndex.hh"
#include "CommonItemSet.hh"
#include "DNSServer.hh"
#include "DOLFileIndex.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh"
#include "GSLArchive.hh"
#include "IPV4RangeSet.hh"
#include "ItemNameIndex.hh"
#include "ItemParameterTable.hh"
#include "ItemTranslationTable.hh"
#include "LevelTable.hh"
#include "Lobby.hh"
#include "MagMetadataTable.hh"
#include "Menu.hh"
#include "Quest.hh"
#include "ShopRandomSets.hh"
#include "TeamIndex.hh"
#include "TekkerAdjustmentSet.hh"
#include "WordSelectTable.hh"
struct DataIndex {
// This structure contains everything which is essentially immutable during the server's uptime - e.g. configuration,
// tables, item definitions, etc. which are only changed by reloading them from disk. Mutable structures, like
// AccountIndex and TeamIndex, are on ServerState instead.
struct PortConfiguration {
std::string name;
std::string addr; // Blank = listen on all interfaces (default)
uint16_t port;
Version version;
ServerBehavior behavior;
};
struct CheatFlags {
// This structure describes which behaviors are considered cheating (that is, require cheat mode to be enabled or the
// user to have the CHEAT_ANYWHERE account flag). A false value here means that that particular behavior is NOT
// cheating, so cheat mode is NOT required.
bool create_items = true;
bool edit_section_id = true;
bool edit_stats = true;
bool ep3_replace_assist = true;
bool ep3_unset_field_character = true;
bool infinite_hp_tp = true;
bool fast_kills = true;
bool insufficient_minimum_level = true;
bool override_random_seed = true;
bool override_section_id = true;
bool override_variations = true;
bool proxy_override_drops = true;
bool reset_materials = false;
bool warp = true;
CheatFlags() = default;
explicit CheatFlags(const phosg::JSON& json);
};
struct BBStreamFile {
struct Entry {
uint32_t offset;
uint32_t size;
uint32_t checksum; // crc32
std::string filename;
};
std::vector<Entry> entries;
std::string data;
};
enum class RunShellBehavior {
DEFAULT = 0,
ALWAYS,
NEVER,
};
enum class BehaviorSwitch {
OFF = 0,
OFF_BY_DEFAULT,
ON_BY_DEFAULT,
ON,
};
static inline bool behavior_enabled(BehaviorSwitch b) {
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
}
static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
}
uint64_t creation_time;
std::string config_filename;
std::shared_ptr<const phosg::JSON> config_json;
bool one_time_config_loaded = false;
size_t num_worker_threads = 0;
std::string name;
std::unordered_map<std::string, PortConfiguration> name_to_port_config;
std::unordered_map<uint16_t, PortConfiguration> number_to_port_config;
std::string username;
std::string dns_server_addr;
uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses;
std::vector<std::string> ppp_raw_addresses;
std::vector<std::string> http_addresses;
uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000;
bool is_debug = false;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;
bool use_temp_accounts_for_prototypes = true;
bool allow_same_account_concurrent_logins = true;
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
bool enable_chat_commands = true;
char chat_command_sentinel = '\0'; // 0 = default (@ on 11/2000; $ on all other versions)
size_t num_backup_character_slots = 16;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint32_t client_customization_name_color = 0x00000000;
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;
ServerDropMode default_drop_mode_v1_v2_normal = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v1_v2_battle = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v1_v2_challenge = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_normal = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_battle = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_challenge = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v4_normal = ServerDropMode::SERVER_SHARED;
ServerDropMode default_drop_mode_v4_battle = ServerDropMode::SERVER_SHARED;
ServerDropMode default_drop_mode_v4_challenge = ServerDropMode::SERVER_SHARED;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
uint64_t persistent_game_idle_timeout_usecs = 0;
std::unordered_map<uint32_t, int64_t> enable_send_function_call_quest_numbers;
bool enable_v3_v4_protected_subcommands = false;
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;
bool censor_credentials = true;
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
bool default_switch_assist_enabled = false;
bool use_game_creator_section_id = false;
bool rare_notifs_enabled_for_client_drops = false;
bool default_rare_notifs_enabled_v1_v2 = false;
bool default_rare_notifs_enabled_v3_v4 = false;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
bool notify_server_for_max_level_achieved = false;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const parray<uint8_t, 0x16C>> bb_default_keyboard_config;
std::shared_ptr<const parray<uint8_t, 0x38>> bb_default_joystick_config;
std::shared_ptr<const ClientFunctionIndex> client_functions;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::unordered_map<uint64_t, std::shared_ptr<const MapFile>> map_file_for_source_hash;
std::map<uint32_t, std::array<std::shared_ptr<const MapFile>, NUM_VERSIONS>> map_files_for_free_play_key;
std::unordered_map<uint64_t, std::shared_ptr<const SuperMap>> supermap_for_source_hash_sum;
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermap_for_free_play_key;
std::shared_ptr<const RoomLayoutIndex> room_layout_index;
std::shared_ptr<const BBStreamFile> bb_stream_file;
std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> quest_index;
std::shared_ptr<const LevelTable> level_table_v1_v2;
std::shared_ptr<const LevelTable> level_table_v3;
std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const CommonItemSet>> common_item_sets;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
std::shared_ptr<const ArmorShopRandomSet> armor_random_set;
std::shared_ptr<const ToolShopRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponShopRandomSet>, 4> weapon_random_sets; // Keyed on difficulty
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::shared_ptr<const ItemTranslationTable> item_translation_table;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
size_t bb_max_bank_items = 200;
size_t bb_max_bank_meseta = 999999;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_dc_nte;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_dc_11_2000;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v1;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v2;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v3;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v4;
std::shared_ptr<const TextIndex> text_index;
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
std::shared_ptr<const WordSelectTable> word_select_table;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
std::array<std::shared_ptr<const MapState::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates_challenge;
std::array<std::array<size_t, 4>, 3> min_levels_v1_v2; // Indexed as [episode][difficulty]
std::array<std::array<size_t, 4>, 3> min_levels_v3; // Indexed as [episode][difficulty]
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
std::unordered_set<std::string> bb_required_patches;
std::unordered_set<std::string> auto_patches;
CheatFlags cheat_flags;
struct QuestF960Result {
uint32_t meseta_cost = 0;
uint32_t base_probability = 0;
uint32_t probability_upgrade = 0;
std::array<std::vector<ItemData>, 7> results;
QuestF960Result() = default;
QuestF960Result(
const phosg::JSON& json, std::shared_ptr<const ItemNameIndex> name_index, const ItemData::StackLimits& limits);
};
// Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results;
float bb_global_exp_multiplier = 1.0f;
float exp_share_multiplier = 0.5f;
float server_global_drop_rate_multiplier = 1.0f;
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::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry {
uint32_t type = 1;
uint32_t which; // See B9 documentation in CommandFormats.hh
std::string data;
};
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
phosg::JSON team_reward_defs_json;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<const std::vector<std::string>> information_contents_v2;
std::shared_ptr<const std::vector<std::string>> information_contents_v3;
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
std::optional<std::pair<std::string, uint16_t>> proxy_destination_patch;
std::optional<std::pair<std::string, uint16_t>> proxy_destination_bb;
std::string welcome_message;
std::string pc_patch_server_message;
std::string bb_patch_server_message;
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
std::vector<uint32_t> client_customization_public_lobby_search_order;
uint8_t pre_lobby_event = 0;
std::vector<uint8_t> per_lobby_events;
int32_t ep3_menu_song = -1;
std::map<std::string, uint32_t> all_addresses;
uint32_t local_address = 0;
uint32_t external_address = 0;
bool proxy_allow_save_files = true;
explicit DataIndex(const std::string& config_filename = "");
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
uint16_t game_server_port_for_version(Version v) const;
std::shared_ptr<const Menu> information_menu(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
std::shared_ptr<const SetDataTableBase> set_data_table(
Version version, Episode episode, GameMode mode, Difficulty difficulty) const;
inline std::shared_ptr<const WeaponShopRandomSet> weapon_random_set(Difficulty difficulty) const {
return this->weapon_random_sets.at(static_cast<size_t>(difficulty));
}
inline std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates(Difficulty difficulty) const {
return this->rare_enemy_rates_by_difficulty.at(static_cast<size_t>(difficulty));
}
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const MagMetadataTable> mag_metadata_table(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
std::string describe_item(Version version, const ItemData& item, uint8_t flags = 0) const;
ItemData parse_item_description(Version version, const std::string& description) const;
std::shared_ptr<const CommonItemSet> common_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
std::shared_ptr<const RareItemSet> rare_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
return this->public_lobby_search_order(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
if (is_client_customization && this->client_customization_name_color) {
return this->client_customization_name_color;
}
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
}
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
return this->name_color_for_client(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
size_t default_min_level_for_game(Version version, Episode episode, Difficulty difficulty) const;
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file(const std::string& patch_index_filename) const;
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
std::pair<std::string, uint16_t> parse_port_spec(const phosg::JSON& json) const;
std::vector<PortConfiguration> parse_port_configuration(const phosg::JSON& json) const;
static constexpr uint32_t free_play_key(
Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities) {
return (static_cast<uint32_t>(episode) << 28) |
(static_cast<uint32_t>(mode) << 26) |
(static_cast<uint32_t>(difficulty) << 24) |
(static_cast<uint32_t>(floor) << 16) |
(static_cast<uint32_t>(layout) << 8) |
(static_cast<uint32_t>(entities) << 0);
}
std::shared_ptr<const SuperMap> get_free_play_supermap(
Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities);
std::vector<std::shared_ptr<const SuperMap>> supermaps_for_variations(
Episode episode, GameMode mode, Difficulty difficulty, const Variations& variations);
void collect_network_addresses();
void load_config_early();
void load_config_late();
void load_bb_private_keys();
void load_bb_system_defaults();
void load_patch_indexes();
void load_maps();
void load_battle_params();
void load_level_tables();
void load_text_index();
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt,
std::shared_ptr<const ItemData::StackLimits> limits,
std::shared_ptr<const TextIndex> text_index) const;
void load_item_name_indexes();
void load_drop_tables();
void load_item_definitions();
void load_set_data_tables();
void load_word_select_table();
void load_ep3_cards();
void load_ep3_maps(bool raise_on_any_failure = false);
void load_quest_index(bool raise_on_any_failure = false);
void compile_functions(bool raise_on_any_failure = false);
void load_dol_files();
void generate_bb_stream_file();
void load_all();
};
+1 -1
View File
@@ -13,7 +13,7 @@
struct Lobby;
class Client;
struct ServerState;
class ServerState;
namespace Episode3 {
+2 -2
View File
@@ -114,7 +114,7 @@ std::vector<std::shared_ptr<Client>> GameServer::get_clients_by_identifier(const
std::shared_ptr<Client> GameServer::create_client(
std::shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
uint32_t addr = ipv4_addr_for_asio_addr(client_sock.remote_endpoint().address());
if (this->state->banned_ipv4_ranges->check(addr)) {
if (this->state->data->banned_ipv4_ranges->check(addr)) {
if (client_sock.is_open()) {
client_sock.close();
}
@@ -129,7 +129,7 @@ std::shared_ptr<Client> GameServer::create_client(
"",
phosg::TerminalFormat::FG_YELLOW,
phosg::TerminalFormat::FG_GREEN,
this->state->censor_credentials,
this->state->data->censor_credentials,
false);
auto c = std::make_shared<Client>(this->shared_from_this(), channel, listen_sock->behavior);
this->log.info_f("Client connected: C-{:X} via {}", c->id, listen_sock->name);
+26 -22
View File
@@ -55,7 +55,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/clients", [this](ArgsT&&) -> RetT {
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
for (const auto& c : this->state->game_server->all_clients()) {
auto item_name_index = this->state->item_name_index_opt(c->version());
auto item_name_index = this->state->data->item_name_index_opt(c->version());
const char* drop_notifications_mode = "unknown";
switch (c->get_drop_notification_mode()) {
@@ -299,7 +299,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
for (const auto& [_, l] : this->state->id_to_lobby) {
auto leader = l->clients[l->leader_id];
Version v = leader ? leader->version() : Version::BB_V4;
auto item_name_index = this->state->item_name_index_opt(v);
auto item_name_index = this->state->data->item_name_index_opt(v);
auto client_ids_json = phosg::JSON::list();
for (size_t z = 0; z < l->max_clients; z++) {
@@ -559,17 +559,17 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
lobby_count++;
}
}
uint64_t uptime_usecs = phosg::now() - this->state->creation_time;
uint64_t uptime_usecs = phosg::now() - this->state->data->creation_time;
return phosg::JSON::dict({
{"StartTimeUsecs", this->state->creation_time},
{"StartTime", phosg::format_time(this->state->creation_time)},
{"StartTimeUsecs", this->state->data->creation_time},
{"StartTime", phosg::format_time(this->state->data->creation_time)},
{"UptimeUsecs", uptime_usecs},
{"Uptime", phosg::format_duration(uptime_usecs)},
{"LobbyCount", lobby_count},
{"GameCount", game_count},
{"ClientCount", this->state->game_server->all_clients().size() - ProxySession::num_proxy_sessions},
{"ProxySessionCount", ProxySession::num_proxy_sessions},
{"ServerName", this->state->name},
{"ServerName", this->state->data->name},
});
};
@@ -578,7 +578,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
});
this->router.add(HTTPRequest::Method::GET, "/y/config", [this](ArgsT&&) -> RetT {
co_return this->state->config_json;
co_return this->state->data->config_json;
});
this->router.add(HTTPRequest::Method::GET, "/y/summary", [this, generate_server_info_json](ArgsT&&) -> RetT {
@@ -644,14 +644,18 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
});
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/cards", [this](ArgsT&& args) -> RetT {
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
auto& index = args.req.query_params.count("trial")
? this->state->data->ep3_card_index_trial
: this->state->data->ep3_card_index;
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
return std::make_shared<phosg::JSON>(index->definitions_json());
});
});
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/card/:card_id", [this](ArgsT&& args) -> RetT {
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
auto& index = args.req.query_params.count("trial")
? this->state->data->ep3_card_index_trial
: this->state->data->ep3_card_index;
uint32_t card_id = args.get_param<uint32_t>("card_id");
try {
co_return std::make_shared<phosg::JSON>(index->definition_for_id(card_id)->def.json());
@@ -663,7 +667,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/maps", [this](ArgsT&&) -> RetT {
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::dict());
for (const auto& [map_number, map] : this->state->ep3_map_index->all_maps()) {
for (const auto& [map_number, map] : this->state->data->ep3_map_index->all_maps()) {
auto languages_json = phosg::JSON::list();
for (const auto& vm : map->all_versions()) {
if (vm) {
@@ -684,7 +688,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language", [this](ArgsT&& args) -> RetT {
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
try {
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number", true));
auto map = this->state->data->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number", true));
auto vm = map->version(language_for_name(args.params.at("language")));
return std::make_shared<phosg::JSON>(vm->map->json(vm->language));
} catch (const std::out_of_range&) {
@@ -696,7 +700,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language/raw", [this](ArgsT&& args) -> RetT {
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> RawResponse {
try {
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number"));
auto map = this->state->data->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number"));
auto vm = map->version(language_for_name(args.params.at("language")));
std::string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
return RawResponse{.content_type = "application/octet-stream", .data = std::move(data)};
@@ -708,7 +712,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/common-tables", [this](ArgsT&&) -> RetT {
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list());
for (const auto& it : this->state->common_item_sets) {
for (const auto& it : this->state->data->common_item_sets) {
ret->emplace_back(it.first);
}
co_return ret;
@@ -716,7 +720,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/common-table/:table_name", [this](ArgsT&& args) -> RetT {
try {
const auto& table = this->state->common_item_sets.at(args.params.at("table_name"));
const auto& table = this->state->data->common_item_sets.at(args.params.at("table_name"));
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
return std::make_shared<phosg::JSON>(table->json());
});
@@ -727,7 +731,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/rare-tables", [this](ArgsT&&) -> RetT {
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list());
for (const auto& it : this->state->rare_item_sets) {
for (const auto& it : this->state->data->rare_item_sets) {
ret->emplace_back(it.first);
}
co_return ret;
@@ -736,16 +740,16 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/rare-table/:table_name", [this](ArgsT&& args) -> RetT {
try {
const auto& table_name = args.params.at("table_name");
const auto& table = this->state->rare_item_sets.at(table_name);
const auto& table = this->state->data->rare_item_sets.at(table_name);
std::shared_ptr<const ItemNameIndex> name_index;
if (table_name.ends_with("-v1")) {
name_index = this->state->item_name_index_opt(Version::DC_V1);
name_index = this->state->data->item_name_index_opt(Version::DC_V1);
} else if (table_name.ends_with("-v2")) {
name_index = this->state->item_name_index_opt(Version::PC_V2);
name_index = this->state->data->item_name_index_opt(Version::PC_V2);
} else if (table_name.ends_with("-v3")) {
name_index = this->state->item_name_index_opt(Version::GC_V3);
name_index = this->state->data->item_name_index_opt(Version::GC_V3);
} else if (table_name.ends_with("-v4")) {
name_index = this->state->item_name_index_opt(Version::BB_V4);
name_index = this->state->data->item_name_index_opt(Version::BB_V4);
}
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
return std::make_shared<phosg::JSON>(table->json(name_index));
@@ -757,13 +761,13 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
this->router.add(HTTPRequest::Method::GET, "/y/data/quests", [this](ArgsT&&) -> RetT {
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
return std::make_shared<phosg::JSON>(this->state->quest_index->json());
return std::make_shared<phosg::JSON>(this->state->data->quest_index->json());
});
});
this->router.add(HTTPRequest::Method::GET, "/y/data/quest/:quest_num", [this](ArgsT&& args) -> RetT {
uint32_t quest_num = args.get_param<uint32_t>("quest_num");
auto q = this->state->quest_index->get(quest_num);
auto q = this->state->data->quest_index->get(quest_num);
if (!q) {
throw HTTPError(404, "Quest does not exist");
}
+7 -7
View File
@@ -145,7 +145,7 @@ void IPSSClient::reschedule_idle_timeout() {
throw std::runtime_error("cannot reschedule idle timeout when simulator is missing");
}
this->idle_timeout_timer.cancel();
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->client_idle_timeout_usecs));
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->data->client_idle_timeout_usecs));
this->idle_timeout_timer.async_wait([this, sim](std::error_code ec) {
if (!ec) {
sim->log.info_f("Idle timeout expired on N-{:X}", this->network_id);
@@ -1358,8 +1358,8 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
std::string conn_str = this->str_for_tcp_connection(c, conn);
// Figure out which logical port the connection should go to
auto port_config_it = this->state->number_to_port_config.find(conn->server_port);
if (port_config_it == this->state->number_to_port_config.end()) {
auto port_config_it = this->state->data->number_to_port_config.find(conn->server_port);
if (port_config_it == this->state->data->number_to_port_config.end()) {
this->log.error_f("TCP connection {} is to undefined port {}", conn_str, conn->server_port);
co_await this->close_tcp_connection(c, conn);
co_return;
@@ -1370,20 +1370,20 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
this->shared_from_this(),
c,
conn,
port_config->version,
port_config.version,
Language::ENGLISH,
"",
phosg::TerminalFormat::END,
phosg::TerminalFormat::END,
false,
this->state->censor_credentials);
this->state->data->censor_credentials);
if (!this->state->game_server.get()) {
this->log.error_f("No server available for TCP connection {}", conn_str);
co_await this->close_tcp_connection(c, conn);
co_return;
} else {
this->state->game_server->connect_channel(conn->server_channel, conn->server_port, port_config->behavior);
this->state->game_server->connect_channel(conn->server_channel, conn->server_port, port_config.behavior);
this->log.info_f("Connected TCP connection {} to game server", conn_str);
}
}
@@ -1403,7 +1403,7 @@ asio::awaitable<void> IPStackSimulator::close_tcp_connection(
std::shared_ptr<IPSSClient> IPStackSimulator::create_client(
std::shared_ptr<IPSSSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
uint32_t addr = ipv4_addr_for_asio_addr(client_sock.remote_endpoint().address());
if (this->state->banned_ipv4_ranges->check(addr)) {
if (this->state->data->banned_ipv4_ranges->check(addr)) {
if (client_sock.is_open()) {
client_sock.close();
}
+11 -11
View File
@@ -21,7 +21,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
// Nothing to do (it should be deleted)
} else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.sh.char_class, item.data.data1[4]);
if (item.data.data1[2] > max_level) {
throw std::runtime_error("technique level too high");
@@ -35,7 +35,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
auto& weapon = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::WEAPON)];
// Only enforce grind limits on BB, since the server doesn't have direct control over inventories on other versions
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
if (is_v4 && (weapon.data.data1[3] >= weapon_def.max_grind)) {
throw std::runtime_error("weapon already at maximum grind");
@@ -100,9 +100,9 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
}
armor.data.data1[5]++;
} else if (item.data.is_wrapped(*s->item_stack_limits(c->version()))) {
} else if (item.data.is_wrapped(*s->data->item_stack_limits(c->version()))) {
// Unwrap present
item.data.unwrap(*s->item_stack_limits(c->version()));
item.data.unwrap(*s->data->item_stack_limits(c->version()));
should_delete_item = false;
} else if (primary_identifier == 0x00330000) {
@@ -131,7 +131,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
} else if ((primary_identifier & 0xFFFF0000) == 0x030C0000) { // Non-combo mag cells
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
uint8_t evolution_number = s->mag_metadata_table(c->version())->get_evolution_number(mag.data.data1[1]);
uint8_t evolution_number = s->data->mag_metadata_table(c->version())->get_evolution_number(mag.data.data1[1]);
if (evolution_number < 4) {
switch (item.data.data1[2]) {
case 0x00: // Cell of MAG 502
@@ -159,7 +159,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
} else if ((primary_identifier & 0xFFFF0000) == 0x03150000) {
// Christmas Present, etc. - use unwrap_table + probabilities therein
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
auto table = item_parameter_table->get_event_items(item.data.data1[2]);
size_t sum = 0;
for (size_t z = 0; z < table.second; z++) {
@@ -198,7 +198,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
// The unopened Hunters Report's rank is stored in the kill count field; using the unopened report copies the rank
// to data1[2] and replaces the inventory item with a new item with the same ID. The game also moves the item to
// the end of the inventory, so we do the same.
const auto& stack_limits = *s->item_stack_limits(c->version());
const auto& stack_limits = *s->data->item_stack_limits(c->version());
auto report_item = player->remove_item(item.data.id, 1, stack_limits);
report_item.data1[2] = report_item.get_kill_count();
player->add_item(report_item, stack_limits);
@@ -213,7 +213,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
continue;
}
try {
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.sh.char_class) {
throw std::runtime_error("item combination requires specific char_class");
@@ -260,7 +260,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
if (should_delete_item) {
// Allow overdrafting meseta if the client is not BB, since the server isn't informed when meseta is added or
// removed from the bank.
player->remove_item(item.data.id, 1, *s->item_stack_limits(c->version()));
player->remove_item(item.data.id, 1, *s->data->item_stack_limits(c->version()));
}
}
@@ -488,8 +488,8 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
apply_mag_feed_result(
player->inventory.items[mag_item_index].data,
player->inventory.items[fed_item_index].data,
s->item_parameter_table(c->version()),
s->mag_metadata_table(c->version()),
s->data->item_parameter_table(c->version()),
s->data->mag_metadata_table(c->version()),
player->disp.visual.sh.char_class,
player->disp.visual.sh.section_id,
!is_v1_or_v2(c->version()));
+14 -14
View File
@@ -172,7 +172,7 @@ uint8_t Lobby::area_for_floor(Version version, uint8_t floor) const {
if (this->quest) {
return this->quest->meta.floor_assignments.at(floor).area;
}
auto sdt = this->require_server_state()->set_data_table(version, this->episode, this->mode, this->difficulty);
auto sdt = this->require_server_state()->data->set_data_table(version, this->episode, this->mode, this->difficulty);
return sdt->default_floor_to_area(this->episode).at(floor);
}
@@ -215,14 +215,14 @@ void Lobby::create_item_creator(Version logic_version) {
effective_section_id = 0x00;
}
this->item_creator = std::make_shared<ItemCreator>(
s->common_item_set(logic_version, this->quest),
s->rare_item_set(logic_version, this->quest),
s->armor_random_set,
s->tool_random_set,
s->weapon_random_set(this->difficulty),
s->tekker_adjustment_set,
s->item_parameter_table(logic_version),
s->item_stack_limits(logic_version),
s->data->common_item_set(logic_version, this->quest),
s->data->rare_item_set(logic_version, this->quest),
s->data->armor_random_set,
s->data->tool_random_set,
s->data->weapon_random_set(this->difficulty),
s->data->tekker_adjustment_set,
s->data->item_parameter_table(logic_version),
s->data->item_stack_limits(logic_version),
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
this->difficulty,
effective_section_id,
@@ -277,7 +277,7 @@ void Lobby::load_maps() {
} else {
this->log.info_f("Loading free play supermaps");
auto s = this->require_server_state();
auto supermaps = s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations);
auto supermaps = s->data->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations);
this->map_state = std::make_shared<MapState>(
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermaps);
}
@@ -307,13 +307,13 @@ void Lobby::create_ep3_server() {
bool is_nte = this->is_ep3_nte();
Episode3::Server::Options options = {
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
.map_index = s->ep3_map_index,
.behavior_flags = s->ep3_behavior_flags,
.card_index = is_nte ? s->data->ep3_card_index_trial : s->data->ep3_card_index,
.map_index = s->data->ep3_map_index,
.behavior_flags = s->data->ep3_behavior_flags,
.opt_rand_stream = nullptr,
.rand_crypt = this->rand_crypt,
.tournament = tourn,
.trap_card_ids = s->ep3_trap_card_ids,
.trap_card_ids = s->data->ep3_trap_card_ids,
.output_queue = nullptr,
};
if (is_nte) {
+1 -1
View File
@@ -20,7 +20,7 @@
#include "StaticGameData.hh"
#include "Text.hh"
struct ServerState;
class ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem {
+266 -208
View File
@@ -2075,10 +2075,10 @@ Action a_print_word_select_table(
given, prints the table sorted by token ID for that version. If no version\n\
option is given, prints the token table sorted by canonical name.\n",
+[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_patch_indexes();
s->load_text_index();
s->load_word_select_table();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_patch_indexes();
di->load_text_index();
di->load_word_select_table();
Version v;
try {
v = get_cli_version(args);
@@ -2086,9 +2086,9 @@ Action a_print_word_select_table(
v = Version::UNKNOWN;
}
if (v != Version::UNKNOWN) {
s->word_select_table->print_index(stdout, v);
di->word_select_table->print_index(stdout, v);
} else {
s->word_select_table->print(stdout);
di->word_select_table->print(stdout);
}
});
@@ -2209,20 +2209,20 @@ Action a_convert_rare_item_set(
drop-anything rate; the true drop rates are shown in tooltips.\n",
+[](phosg::Arguments& args) {
double rate_factor = args.get<double>("multiply", 1.0);
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_text_index();
s->load_item_definitions();
s->load_item_name_indexes();
s->load_drop_tables();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_text_index();
di->load_item_definitions();
di->load_item_name_indexes();
di->load_drop_tables();
std::string input_filename = args.get<std::string>(1, false);
if (input_filename.empty() || (input_filename == "-")) {
throw std::runtime_error("input filename must be given");
}
auto rs = load_rare_item_set(
input_filename, is_v1(get_cli_version(args, Version::BB_V4)), s->item_name_index(Version::BB_V4));
input_filename, is_v1(get_cli_version(args, Version::BB_V4)), di->item_name_index(Version::BB_V4));
if (rate_factor != 1.0) {
rs->multiply_all_rates(rate_factor);
}
@@ -2230,9 +2230,9 @@ Action a_convert_rare_item_set(
std::string output_filename = args.get<std::string>(2, false);
std::string output_filename_lower = phosg::tolower(output_filename);
if (output_filename.empty() || (output_filename == "-")) {
rs->print_all_collections(stdout, s->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
rs->print_all_collections(stdout, di->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
} else if (output_filename_lower.ends_with(".json")) {
auto json = rs->json(s->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
auto json = rs->json(di->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
std::string data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
write_output_data(args, data, nullptr);
} else if (output_filename_lower.ends_with(".gsl")) {
@@ -2251,8 +2251,8 @@ Action a_convert_rare_item_set(
if ((is_v1 && (difficulty == Difficulty::ULTIMATE)) || (!rs->has_entries_for_game_config(mode, episode, difficulty))) {
continue;
}
auto item_name_index = s->item_name_index(cli_version);
std::string data = rs->serialize_html(mode, episode, difficulty, item_name_index, s->common_item_set(cli_version, nullptr));
auto item_name_index = di->item_name_index(cli_version);
std::string data = rs->serialize_html(mode, episode, difficulty, item_name_index, di->common_item_set(cli_version, nullptr));
std::string out_filename = output_filename.substr(0, output_filename.size() - 5) + "." + name_for_mode(mode) + "." + abbreviation_for_episode(episode) + "." + abbreviation_for_difficulty(difficulty) + output_filename.substr(output_filename.size() - 5);
phosg::save_file(out_filename, data);
phosg::log_info_f("... {}", out_filename);
@@ -2275,17 +2275,17 @@ Action a_compare_rare_item_set(
throw std::runtime_error("two input filenames must be given");
}
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_text_index();
s->load_item_definitions();
s->load_item_name_indexes();
s->load_drop_tables();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_text_index();
di->load_item_definitions();
di->load_item_name_indexes();
di->load_drop_tables();
bool is_v1 = ::is_v1(get_cli_version(args, Version::BB_V4));
auto rs1 = load_rare_item_set(input_filename1, is_v1, s->item_name_index(Version::BB_V4));
auto rs2 = load_rare_item_set(input_filename2, is_v1, s->item_name_index(Version::BB_V4));
auto rs1 = load_rare_item_set(input_filename1, is_v1, di->item_name_index(Version::BB_V4));
auto rs2 = load_rare_item_set(input_filename2, is_v1, di->item_name_index(Version::BB_V4));
rs1->print_diff(stdout, *rs2);
});
@@ -2622,13 +2622,13 @@ Action a_describe_item(
std::string description = args.get<std::string>(1);
auto version = get_cli_version(args);
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_text_index();
s->load_item_definitions();
s->load_item_name_indexes();
auto name_index = s->item_name_index(version);
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_text_index();
di->load_item_definitions();
di->load_item_name_indexes();
auto name_index = di->item_name_index(version);
ItemData item = name_index->parse_item_description(description);
@@ -2646,7 +2646,7 @@ Action a_describe_item(
item.data2[0], item.data2[1], item.data2[2], item.data2[3]);
ItemData item_v2 = item;
item_v2.encode_for_version(Version::PC_V2, s->item_parameter_table_for_encode(Version::PC_V2));
item_v2.encode_for_version(Version::PC_V2, di->item_parameter_table_for_encode(Version::PC_V2));
ItemData item_v2_decoded = item_v2;
item_v2_decoded.decode_for_version(Version::PC_V2);
@@ -2665,7 +2665,7 @@ Action a_describe_item(
}
ItemData item_gc = item;
item_gc.encode_for_version(Version::GC_V3, s->item_parameter_table_for_encode(Version::GC_V3));
item_gc.encode_for_version(Version::GC_V3, di->item_parameter_table_for_encode(Version::GC_V3));
ItemData item_gc_decoded = item_gc;
item_gc_decoded.decode_for_version(Version::GC_V3);
@@ -2686,24 +2686,24 @@ Action a_describe_item(
phosg::log_info_f("Description: {}", desc);
phosg::log_info_f("Description (in-game): {}", desc_colored);
size_t purchase_price = s->item_parameter_table(Version::BB_V4)->price_for_item(item);
size_t purchase_price = di->item_parameter_table(Version::BB_V4)->price_for_item(item);
size_t sale_price = purchase_price >> 3;
phosg::log_info_f("Purchase price: {}; sale price: {}", purchase_price, sale_price);
});
Action a_name_all_items(
"name-all-items", nullptr, +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_text_index();
s->load_item_definitions();
s->load_item_name_indexes();
s->load_ep3_cards();
s->load_config_late();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_text_index();
di->load_item_definitions();
di->load_item_name_indexes();
di->load_ep3_cards();
di->load_config_late();
std::set<uint32_t> all_primary_identifiers;
for (const auto& index : s->item_name_indexes) {
for (const auto& index : di->item_name_indexes) {
if (index) {
for (const auto& it : index->all_by_primary_identifier()) {
all_primary_identifiers.emplace(it.first);
@@ -2715,10 +2715,10 @@ Action a_name_all_items(
for (uint32_t primary_identifier : all_primary_identifiers) {
phosg::fwrite_fmt(stdout, "{:08X}\n", primary_identifier);
for (Version v : ALL_VERSIONS) {
const auto& index = s->item_name_index_opt(v);
const auto& index = di->item_name_index_opt(v);
if (index) {
auto pmt = s->item_parameter_table(v);
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier);
auto pmt = di->item_parameter_table(v);
ItemData item = ItemData::from_primary_identifier(*di->item_stack_limits(v), primary_identifier);
std::string name = index->describe_item(item);
try {
bool is_rare = pmt->is_item_rare(item);
@@ -2736,7 +2736,7 @@ Action a_name_all_items(
auto print_header = [&]() -> void {
phosg::fwrite_fmt(stdout, "IDENT :");
for (Version v : ALL_VERSIONS) {
const auto& index = s->item_name_index_opt(v);
const auto& index = di->item_name_index_opt(v);
if (index) {
phosg::fwrite_fmt(stdout, " {:30} ", phosg::name_for_enum(v));
}
@@ -2755,10 +2755,10 @@ Action a_name_all_items(
phosg::fwrite_fmt(stdout, "{:08X}:", primary_identifier);
for (Version v : ALL_VERSIONS) {
const auto& index = s->item_name_index_opt(v);
const auto& index = di->item_name_index_opt(v);
if (index) {
auto pmt = s->item_parameter_table(v);
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier);
auto pmt = di->item_parameter_table(v);
ItemData item = ItemData::from_primary_identifier(*di->item_stack_limits(v), primary_identifier);
if (index->exists(item)) {
std::string name = index->describe_item(item);
bool is_rare = pmt->is_item_rare(item);
@@ -2778,10 +2778,10 @@ Action a_print_level_stats(
show-level-tables\n\
Print the level tables for each version in a semi-human-readable format.\n",
+[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_level_tables();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_level_tables();
std::vector<PlayerStats> level_1_v1_v2;
std::vector<PlayerStats> level_100_v1_v2;
@@ -2795,19 +2795,19 @@ Action a_print_level_stats(
std::vector<PlayerStats> level_200_limit_v4;
for (size_t z = 0; z < 12; z++) {
if (z < 9) {
level_1_v1_v2.emplace_back().char_stats = s->level_table_v1_v2->base_stats_for_class(z);
level_200_limit_v1_v2.emplace_back(s->level_table_v1_v2->max_stats_for_class(z));
s->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
s->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
level_1_v1_v2.emplace_back().char_stats = di->level_table_v1_v2->base_stats_for_class(z);
level_200_limit_v1_v2.emplace_back(di->level_table_v1_v2->max_stats_for_class(z));
di->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
di->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
}
level_1_v3.emplace_back().char_stats = s->level_table_v3->base_stats_for_class(z);
s->level_table_v3->advance_to_level(level_200_v3.emplace_back(level_1_v3.back()), 199, z);
level_200_limit_v3.emplace_back(s->level_table_v3->max_stats_for_class(z));
level_1_v3.emplace_back().char_stats = di->level_table_v3->base_stats_for_class(z);
di->level_table_v3->advance_to_level(level_200_v3.emplace_back(level_1_v3.back()), 199, z);
level_200_limit_v3.emplace_back(di->level_table_v3->max_stats_for_class(z));
level_1_v4.emplace_back().char_stats = s->level_table_v4->base_stats_for_class(z);
s->level_table_v4->advance_to_level(level_200_v4.emplace_back(level_1_v3.back()), 199, z);
level_200_limit_v4.emplace_back(s->level_table_v4->max_stats_for_class(z));
level_1_v4.emplace_back().char_stats = di->level_table_v4->base_stats_for_class(z);
di->level_table_v4->advance_to_level(level_200_v4.emplace_back(level_1_v3.back()), 199, z);
level_200_limit_v4.emplace_back(di->level_table_v4->max_stats_for_class(z));
}
auto print_stats_set = [](const std::vector<PlayerStats>& stats_vec, const char* name) -> void {
@@ -2865,10 +2865,10 @@ Action a_show_item_parameter_tables(
Print the item parameter tables for each version in a semi-human-readable\n\
format.\n",
+[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_all(false);
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_all();
for (Version v : ALL_VERSIONS) {
const auto& index = s->item_name_index_opt(v);
const auto& index = di->item_name_index_opt(v);
if (index) {
phosg::fwrite_fmt(stdout, "======== {}\n", phosg::name_for_enum(v));
index->print_table(stdout);
@@ -2882,19 +2882,19 @@ Action a_show_shop_random_sets(
Print the tekker and shop generation tables in a semi-human-readable\n\
format.\n",
+[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_all(false);
s->tekker_adjustment_set->print(stdout);
s->armor_random_set->print(stdout);
s->tool_random_set->print(stdout);
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_all();
di->tekker_adjustment_set->print(stdout);
di->armor_random_set->print(stdout);
di->tool_random_set->print(stdout);
phosg::fwrite_fmt(stdout, "(Normal) ");
s->weapon_random_set(Difficulty::NORMAL)->print(stdout);
di->weapon_random_set(Difficulty::NORMAL)->print(stdout);
phosg::fwrite_fmt(stdout, "(Hard) ");
s->weapon_random_set(Difficulty::HARD)->print(stdout);
di->weapon_random_set(Difficulty::HARD)->print(stdout);
phosg::fwrite_fmt(stdout, "(Very Hard) ");
s->weapon_random_set(Difficulty::VERY_HARD)->print(stdout);
di->weapon_random_set(Difficulty::VERY_HARD)->print(stdout);
phosg::fwrite_fmt(stdout, "(Ultimate) ");
s->weapon_random_set(Difficulty::ULTIMATE)->print(stdout);
di->weapon_random_set(Difficulty::ULTIMATE)->print(stdout);
});
Action a_show_ep3_cards(
@@ -2905,8 +2905,8 @@ Action a_show_ep3_cards(
+[](phosg::Arguments& args) {
bool one_line = args.get<bool>("one-line");
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_ep3_cards();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_ep3_cards();
std::unique_ptr<BinaryTextSet> text_english;
try {
@@ -2915,10 +2915,10 @@ Action a_show_ep3_cards(
} catch (const std::exception& e) {
}
auto card_ids = s->ep3_card_index->all_ids();
auto card_ids = di->ep3_card_index->all_ids();
phosg::log_info_f("{} card definitions", card_ids.size());
for (uint32_t card_id : card_ids) {
auto entry = s->ep3_card_index->definition_for_id(card_id);
auto entry = di->ep3_card_index->definition_for_id(card_id);
phosg::fwrite_fmt(stdout, "{}\n", entry->def.str(one_line, text_english.get()));
if (!one_line) {
if (!entry->debug_tags.empty()) {
@@ -2957,14 +2957,14 @@ Action a_generate_ep3_cards_html(
bool no_large_images = args.get<bool>("no-large-images");
bool no_disassembly = args.get<bool>("no-disassembly");
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_patch_indexes();
s->load_text_index();
s->load_ep3_cards();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_patch_indexes();
di->load_text_index();
di->load_ep3_cards();
std::shared_ptr<const TextSet> text_english;
try {
text_english = s->text_index->get(Version::GC_EP3, Language::ENGLISH);
text_english = di->text_index->get(Version::GC_EP3, Language::ENGLISH);
} catch (const std::out_of_range&) {
}
@@ -3063,10 +3063,10 @@ Action a_generate_ep3_cards_html(
std::vector<VersionInfo> version_infos;
if (include_nte) {
version_infos.emplace_back("NTE", s->ep3_card_index_trial, no_images ? nullptr : "system/ep3/cardtex-trial", no_large_images, num_threads, no_disassembly);
version_infos.emplace_back("NTE", di->ep3_card_index_trial, no_images ? nullptr : "system/ep3/cardtex-trial", no_large_images, num_threads, no_disassembly);
}
if (include_final) {
version_infos.emplace_back("Final", s->ep3_card_index, no_images ? nullptr : "system/ep3/cardtex", no_large_images, num_threads, no_disassembly);
version_infos.emplace_back("Final", di->ep3_card_index, no_images ? nullptr : "system/ep3/cardtex", no_large_images, num_threads, no_disassembly);
}
std::deque<std::string> blocks;
@@ -3199,11 +3199,11 @@ Action a_show_ep3_maps(
+[](phosg::Arguments& args) {
config_log.info_f("Collecting Episode 3 data");
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_ep3_cards();
s->load_ep3_maps();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_ep3_cards();
di->load_ep3_maps();
const auto& all_maps = s->ep3_map_index->all_maps();
const auto& all_maps = di->ep3_map_index->all_maps();
phosg::log_info_f("{} maps", all_maps.size());
for (const auto& [map_number, map] : all_maps) {
const auto& vms = map->all_versions();
@@ -3212,7 +3212,7 @@ Action a_show_ep3_maps(
continue;
}
Language language = static_cast<Language>(lang_index);
std::string map_s = vms[lang_index]->map->str(s->ep3_card_index.get(), language);
std::string map_s = vms[lang_index]->map->str(di->ep3_card_index.get(), language);
phosg::fwrite_fmt(stdout, "({}) {}\n", char_for_language(language), map_s);
}
}
@@ -3224,22 +3224,22 @@ Action a_show_battle_params(
Print the Blue Burst battle parameters from the system/blueburst directory\n\
in a human-readable format.\n",
+[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_patch_indexes();
s->load_battle_params();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_patch_indexes();
di->load_battle_params();
phosg::fwrite_fmt(stdout, "Episode 1 multi\n");
s->battle_params->get_table(false, Episode::EP1).print(stdout, Episode::EP1);
di->battle_params->get_table(false, Episode::EP1).print(stdout, Episode::EP1);
phosg::fwrite_fmt(stdout, "Episode 1 solo\n");
s->battle_params->get_table(true, Episode::EP1).print(stdout, Episode::EP1);
di->battle_params->get_table(true, Episode::EP1).print(stdout, Episode::EP1);
phosg::fwrite_fmt(stdout, "Episode 2 multi\n");
s->battle_params->get_table(false, Episode::EP2).print(stdout, Episode::EP2);
di->battle_params->get_table(false, Episode::EP2).print(stdout, Episode::EP2);
phosg::fwrite_fmt(stdout, "Episode 2 solo\n");
s->battle_params->get_table(true, Episode::EP2).print(stdout, Episode::EP2);
di->battle_params->get_table(true, Episode::EP2).print(stdout, Episode::EP2);
phosg::fwrite_fmt(stdout, "Episode 4 multi\n");
s->battle_params->get_table(false, Episode::EP4).print(stdout, Episode::EP4);
di->battle_params->get_table(false, Episode::EP4).print(stdout, Episode::EP4);
phosg::fwrite_fmt(stdout, "Episode 4 solo\n");
s->battle_params->get_table(true, Episode::EP4).print(stdout, Episode::EP4);
di->battle_params->get_table(true, Episode::EP4).print(stdout, Episode::EP4);
});
Action a_check_supermaps(
@@ -3253,11 +3253,11 @@ Action a_check_supermaps(
bool save_disassembly = args.get<bool>("disassemble");
bool generate_enemy_stats = args.get<bool>("generate-enemy-stats");
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_set_data_tables();
s->load_maps();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_set_data_tables();
di->load_maps();
auto rand_crypt = std::make_shared<MT19937Generator>(phosg::random_object<uint32_t>());
@@ -3273,9 +3273,9 @@ Action a_check_supermaps(
abbreviation_for_mode(mode),
abbreviation_for_difficulty(difficulty));
auto sdt = s->set_data_table(Version::BB_V4, episode, mode, difficulty);
auto sdt = di->set_data_table(Version::BB_V4, episode, mode, difficulty);
auto variations = sdt->generate_variations(episode, (mode == GameMode::SOLO), rand_crypt);
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations);
auto supermaps = di->supermaps_for_variations(episode, mode, difficulty, variations);
auto map_state = std::make_shared<MapState>(
0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps);
map_state->verify();
@@ -3288,7 +3288,7 @@ Action a_check_supermaps(
}
SuperMap::EfficiencyStats all_free_maps_eff;
for (const auto& [key, supermap] : s->supermap_for_free_play_key) {
for (const auto& [key, supermap] : di->supermap_for_free_play_key) {
auto episode = static_cast<Episode>((key >> 28) & 7);
auto mode = static_cast<GameMode>((key >> 26) & 3);
Difficulty difficulty = static_cast<Difficulty>((key >> 24) & 3);
@@ -3332,11 +3332,11 @@ Action a_check_supermaps(
phosg::fwrite_fmt(stderr, "ALL FREE MAPS: {}\n", all_free_maps_eff.str());
s->load_quest_index();
di->load_quest_index();
SuperMap::EfficiencyStats all_quests_eff;
uint32_t random_seed = args.get<uint32_t>("random-seed", 0, phosg::Arguments::IntFormat::HEX);
for (const auto& it : s->quest_index->quests_by_number) {
for (const auto& it : di->quest_index->quests_by_number) {
auto supermap = it.second->get_supermap(random_seed);
if (!supermap) {
throw std::logic_error("quest does not have a supermap, even with a specified random seed");
@@ -3648,11 +3648,11 @@ Action a_print_free_supermap(
}
}
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_config_early();
s->load_patch_indexes();
s->load_set_data_tables();
s->load_maps();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_config_early();
di->load_patch_indexes();
di->load_set_data_tables();
di->load_maps();
std::shared_ptr<RandomGenerator> rand_crypt;
if (args.get<bool>("--psov2")) {
@@ -3660,8 +3660,8 @@ Action a_print_free_supermap(
} else {
rand_crypt = std::make_shared<PSOV2Encryption>(random_seed);
}
auto sdt = s->set_data_table(get_cli_version(args, Version::BB_V4), episode, mode, difficulty);
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations);
auto sdt = di->set_data_table(get_cli_version(args, Version::BB_V4), episode, mode, difficulty);
auto supermaps = di->supermaps_for_variations(episode, mode, difficulty, variations);
MapState map_state(0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps);
map_state.verify();
map_state.print(stdout);
@@ -3677,13 +3677,13 @@ Action a_check_quests(
check_quest_opcode_definitions();
phosg::log_info_f("Opcode definitions OK");
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->is_debug = true;
s->load_config_early();
s->load_patch_indexes();
s->load_set_data_tables();
s->load_maps();
s->load_quest_index(true);
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->is_debug = true;
di->load_config_early();
di->load_patch_indexes();
di->load_set_data_tables();
di->load_maps();
di->load_quest_index(true);
uint64_t script_time = 0, map_time = 0;
if (reassemble_scripts || reassemble_maps) {
@@ -3797,7 +3797,7 @@ Action a_check_quests(
};
if (num_threads == 1) {
for (const auto& [_, q] : s->quest_index->quests_by_number) {
for (const auto& [_, q] : di->quest_index->quests_by_number) {
for (const auto& [_, vq] : q->versions) {
check_vq(vq, 0);
}
@@ -3805,7 +3805,7 @@ Action a_check_quests(
} else {
std::vector<std::shared_ptr<const VersionedQuest>> all_vqs;
for (const auto& [_, q] : s->quest_index->quests_by_number) {
for (const auto& [_, q] : di->quest_index->quests_by_number) {
for (const auto& [_, vq] : q->versions) {
all_vqs.emplace_back(vq);
}
@@ -3832,9 +3832,9 @@ Action a_check_ep3_maps(
"check-ep3-maps", nullptr,
+[](phosg::Arguments& args) {
config_log.info_f("Collecting Episode 3 data");
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->is_debug = true;
s->load_ep3_maps(true);
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->is_debug = true;
di->load_ep3_maps(true);
});
Action a_check_client_functions(
@@ -4029,9 +4029,9 @@ Action a_format_ep3_battle_record(
Action a_replay_ep3_battle_commands(
"replay-ep3-battle-commands", nullptr, +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_ep3_cards();
s->load_ep3_maps();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_ep3_cards();
di->load_ep3_maps();
int64_t base_seed = args.get<int64_t>("seed", -1);
bool is_trial = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE);
@@ -4047,8 +4047,8 @@ Action a_replay_ep3_battle_commands(
auto run_replay = [&](int64_t seed, size_t) {
Episode3::Server::Options options = {
.card_index = s->ep3_card_index,
.map_index = s->ep3_map_index,
.card_index = di->ep3_card_index,
.map_index = di->ep3_map_index,
.behavior_flags = 0x0092,
.opt_rand_stream = nullptr,
.rand_crypt = std::make_shared<MT19937Generator>(seed),
@@ -4088,15 +4088,15 @@ Action a_replay_ep3_battle_record(
bool use_color = isatty(fileno(stdout));
auto s = std::make_shared<ServerState>(get_config_filename(args));
s->load_ep3_cards();
s->load_ep3_maps();
auto di = std::make_shared<DataIndex>(get_config_filename(args));
di->load_ep3_cards();
di->load_ep3_maps();
bool is_nte = rec->get_behavior_flags() & Episode3::BehaviorFlag::IS_TRIAL_EDITION;
auto output_queue = std::make_shared<std::deque<std::string>>();
Episode3::Server::Options options = {
.card_index = s->ep3_card_index,
.map_index = s->ep3_map_index,
.card_index = di->ep3_card_index,
.map_index = di->ep3_map_index,
.behavior_flags = rec->get_behavior_flags() & ~(Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING),
.opt_rand_stream = std::make_shared<phosg::StringReader>(rec->get_random_stream()),
.rand_crypt = std::make_shared<DisabledRandomGenerator>(),
@@ -4220,7 +4220,20 @@ Action a_run_server_replay_log(
std::filesystem::create_directories("system/players");
}
const auto& replay_log_filenames = args.get_multi<std::string>("replay-log");
const auto& args_replay_log_filenames = args.get_multi<std::string>("replay-log");
std::vector<std::string> replay_log_filenames;
for (auto& log_filename : args_replay_log_filenames) {
if (std::filesystem::is_directory(log_filename)) {
for (const auto& item : std::filesystem::directory_iterator(log_filename)) {
std::string test_filename = item.path().filename().string();
if (test_filename.ends_with(".test.txt")) {
replay_log_filenames.emplace_back(std::format("{}/{}", log_filename, test_filename));
}
}
} else {
replay_log_filenames.emplace_back(std::move(log_filename));
}
}
#ifndef PHOSG_WINDOWS
signal(SIGPIPE, SIG_IGN);
@@ -4229,123 +4242,157 @@ Action a_run_server_replay_log(
use_terminal_colors = true;
}
auto state = std::make_shared<ServerState>(get_config_filename(args), !replay_log_filenames.empty());
auto data_index = std::make_shared<DataIndex>(get_config_filename(args));
if (args.get<bool>("debug")) {
state->is_debug = true;
}
state->load_all(true);
if (state->dns_server_port) {
if (!state->dns_server_addr.empty()) {
config_log.info_f("Starting DNS server on {}:{}", state->dns_server_addr, state->dns_server_port);
} else {
config_log.info_f("Starting DNS server on port {}", state->dns_server_port);
}
state->dns_server = std::make_shared<DNSServer>(state);
state->dns_server->listen(state->dns_server_addr, state->dns_server_port);
} else {
config_log.info_f("DNS server is disabled");
data_index->is_debug = true;
}
data_index->load_all();
auto state = ServerState::create_shared(data_index, !replay_log_filenames.empty());
std::shared_ptr<ServerShell> shell;
std::shared_ptr<SignalWatcher> signal_watcher;
std::shared_ptr<ReplaySession> last_running_replay;
std::map<std::string, std::shared_ptr<ReplaySession>> replay_sessions;
if (!replay_log_filenames.empty()) {
config_log.info_f("Starting game server");
// TODO: Do this properly via a config option, you lazy bum
state->data->dol_file_index = std::make_shared<DOLFileIndex>();
state->game_server = std::make_shared<GameServer>(state);
// TODO: Do this properly via a config option, you lazy bum
state->dol_file_index = std::make_shared<DOLFileIndex>();
if (args.get<bool>("parallel")) {
size_t completed_count = 0;
auto run_replay = [&](const std::string& log_filename) -> asio::awaitable<void> {
auto replay_state = state->clone_shared();
replay_state->game_server = std::make_shared<GameServer>(replay_state);
auto run_replays = [&]() -> asio::awaitable<void> {
try {
for (const auto& log_filename : replay_log_filenames) {
phosg::log_info_f("[Replay] {} ...", log_filename);
auto log_f = phosg::fopen_shared(log_filename, "rt");
last_running_replay = std::make_shared<ReplaySession>(state, log_f.get());
co_await last_running_replay->run();
if (last_running_replay->failed()) {
phosg::log_error_f("[Replay] {} failed", log_filename);
break;
}
phosg::log_info_f("[Replay] Loading {}", log_filename);
auto log_f = phosg::fopen_unique(log_filename, "rt");
auto replay_session = std::make_shared<ReplaySession>(replay_state, log_f.get());
replay_sessions.emplace(log_filename, replay_session);
phosg::log_info_f("[Replay] {} ...", log_filename);
co_await replay_session->run();
if (!replay_session->failure_str().empty()) {
phosg::log_error_f("[Replay] {} failed:\n{}", log_filename, replay_session->failure_str());
} else {
phosg::log_info_f("[Replay] {} OK", log_filename);
state->reset_between_replays();
}
phosg::log_info_f("[Replay] All replays complete");
} catch (const std::exception& e) {
phosg::log_info_f("[Replay] Replays failed: {}", e.what());
completed_count++;
if (completed_count == replay_log_filenames.size()) {
phosg::log_info_f("[Replay] All replays complete; exiting");
state->io_context->stop();
}
};
for (const auto& log_filename : replay_log_filenames) {
asio::co_spawn(*state->io_context, run_replay(log_filename), asio::detached);
}
if (!last_running_replay->failed()) {
last_running_replay.reset();
} else {
for (const auto& log_filename : replay_log_filenames) {
phosg::log_info_f("[Replay] Loading {}", log_filename);
auto log_f = phosg::fopen_unique(log_filename, "rt");
replay_sessions.emplace(log_filename, std::make_shared<ReplaySession>(state, log_f.get()));
}
state->io_context->stop();
};
asio::co_spawn(*state->io_context, run_replays, asio::detached);
auto run_replays = [&]() -> asio::awaitable<void> {
try {
for (const auto& [log_filename, replay_session] : replay_sessions) {
phosg::log_info_f("[Replay] {} ...", log_filename);
co_await replay_session->run();
if (!replay_session->failure_str().empty()) {
phosg::log_error_f("[Replay] {} failed:\n{}", log_filename, replay_session->failure_str());
break;
}
phosg::log_info_f("[Replay] {} OK", log_filename);
state->reset_between_replays();
}
phosg::log_info_f("[Replay] All replays complete");
} catch (const std::exception& e) {
phosg::log_info_f("[Replay] Replays failed: {}", e.what());
}
state->io_context->stop();
};
asio::co_spawn(*state->io_context, run_replays, asio::detached);
}
} else {
if (state->data->dns_server_port) {
if (!state->data->dns_server_addr.empty()) {
config_log.info_f("Starting DNS server on {}:{}", state->data->dns_server_addr, state->data->dns_server_port);
} else {
config_log.info_f("Starting DNS server on port {}", state->data->dns_server_port);
}
state->dns_server = std::make_shared<DNSServer>(state);
state->dns_server->listen(state->data->dns_server_addr, state->data->dns_server_port);
} else {
config_log.info_f("DNS server is disabled");
}
config_log.info_f("Opening sockets");
for (const auto& [_, pc] : state->name_to_port_config) {
for (const auto& [_, pc] : state->data->name_to_port_config) {
if (!state->game_server.get()) {
config_log.info_f("Starting game server");
state->game_server = std::make_shared<GameServer>(state);
}
std::string spec = std::format("TG-{}-{}-{}-{}",
pc->port, phosg::name_for_enum(pc->version), pc->name, phosg::name_for_enum(pc->behavior));
state->game_server->listen(spec, pc->addr, pc->port, pc->version, pc->behavior);
pc.port, phosg::name_for_enum(pc.version), pc.name, phosg::name_for_enum(pc.behavior));
state->game_server->listen(spec, pc.addr, pc.port, pc.version, pc.behavior);
}
if (!state->ip_stack_addresses.empty() || !state->ppp_stack_addresses.empty() || !state->ppp_raw_addresses.empty()) {
if (!state->data->ip_stack_addresses.empty() ||
!state->data->ppp_stack_addresses.empty() ||
!state->data->ppp_raw_addresses.empty()) {
config_log.info_f("Starting IP/PPP stack simulator");
state->ip_stack_simulator = std::make_shared<IPStackSimulator>(state);
for (const auto& it : state->ip_stack_addresses) {
for (const auto& it : state->data->ip_stack_addresses) {
auto netloc = phosg::parse_netloc(it);
std::string spec = (netloc.second == 0) ? ("T-IPS-" + netloc.first) : std::format("T-IPS-{}", netloc.second);
state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, VirtualNetworkProtocol::ETHERNET_TAPSERVER);
}
for (const auto& it : state->ppp_stack_addresses) {
for (const auto& it : state->data->ppp_stack_addresses) {
auto netloc = phosg::parse_netloc(it);
std::string spec = (netloc.second == 0) ? ("T-PPPST-" + netloc.first) : std::format("T-PPPST-{}", netloc.second);
state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_TAPSERVER);
}
for (const auto& it : state->ppp_raw_addresses) {
for (const auto& it : state->data->ppp_raw_addresses) {
auto netloc = phosg::parse_netloc(it);
std::string spec = (netloc.second == 0) ? ("T-PPPSR-" + netloc.first) : std::format("T-PPPSR-{}", netloc.second);
state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_RAW);
if (netloc.second) {
if (state->local_address == 0 && state->external_address == 0) {
if (state->data->local_address == 0 && state->data->external_address == 0) {
config_log.info_f(
"Cannot generate Devolution phone numbers for {} because LocalAddress and ExternalAddress are not specified in the configuration",
spec);
} else if (state->local_address == 0) {
} else if (state->data->local_address == 0) {
config_log.info_f(
"Note: The Devolution phone number for {} is {} (external)",
spec, devolution_phone_number_for_netloc(state->external_address, netloc.second));
} else if (state->external_address == 0) {
spec, devolution_phone_number_for_netloc(state->data->external_address, netloc.second));
} else if (state->data->external_address == 0) {
config_log.info_f(
"Note: The Devolution phone number for {} is {} (local)",
spec, devolution_phone_number_for_netloc(state->local_address, netloc.second));
} else if (state->local_address == state->external_address) {
spec, devolution_phone_number_for_netloc(state->data->local_address, netloc.second));
} else if (state->data->local_address == state->data->external_address) {
config_log.info_f(
"Note: The Devolution phone number for {} is {} (local+external)",
spec, devolution_phone_number_for_netloc(state->local_address, netloc.second));
spec, devolution_phone_number_for_netloc(state->data->local_address, netloc.second));
} else {
config_log.info_f(
"Note: The Devolution phone numbers for {} are {} (local) and {} (external)",
spec,
devolution_phone_number_for_netloc(state->local_address, netloc.second),
devolution_phone_number_for_netloc(state->external_address, netloc.second));
devolution_phone_number_for_netloc(state->data->local_address, netloc.second),
devolution_phone_number_for_netloc(state->data->external_address, netloc.second));
}
}
}
}
if (!state->http_addresses.empty() || !state->http_addresses.empty()) {
if (!state->data->http_addresses.empty()) {
config_log.info_f("Starting HTTP server");
state->http_server = std::make_shared<HTTPServer>(state);
for (const auto& it : state->http_addresses) {
for (const auto& it : state->data->http_addresses) {
auto netloc = phosg::parse_netloc(it);
state->http_server->listen(netloc.first, netloc.second);
}
@@ -4358,16 +4405,16 @@ Action a_run_server_replay_log(
}
#ifndef PHOSG_WINDOWS
if (!state->username.empty()) {
config_log.info_f("Switching to user {}", state->username);
drop_privileges(state->username);
if (!state->data->username.empty()) {
config_log.info_f("Switching to user {}", state->data->username);
drop_privileges(state->data->username);
}
#endif
bool should_run_shell;
if (state->run_shell_behavior == ServerState::RunShellBehavior::DEFAULT) {
if (state->data->run_shell_behavior == DataIndex::RunShellBehavior::DEFAULT) {
should_run_shell = isatty(fileno(stdin));
} else if (state->run_shell_behavior == ServerState::RunShellBehavior::ALWAYS) {
} else if (state->data->run_shell_behavior == DataIndex::RunShellBehavior::ALWAYS) {
should_run_shell = true;
} else {
should_run_shell = false;
@@ -4384,8 +4431,19 @@ Action a_run_server_replay_log(
state->io_context->run();
config_log.info_f("Normal shutdown");
if (last_running_replay) {
throw std::runtime_error("Replay failed");
if (!replay_sessions.empty()) {
size_t num_failed_replays = 0;
for (const auto& [log_filename, replay_session] : replay_sessions) {
if (!replay_session->failure_str().empty()) {
config_log.warning_f("Replay failed: {}", log_filename);
num_failed_replays++;
}
}
if (num_failed_replays) {
throw std::runtime_error(std::format("{}/{} replays failed", num_failed_replays, replay_sessions.size()));
} else {
config_log.info_f("All {} replays succeeded", replay_sessions.size());
}
}
});
+4 -4
View File
@@ -980,7 +980,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(std::shared_ptr<Client> c, Ch
c->log.info_f("No item was created");
} else {
auto s = c->require_server_state();
std::string name = s->describe_item(c->version(), res.item);
std::string name = s->data->describe_item(c->version(), res.item);
c->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
res.item.id = c->proxy_session->next_item_id++;
c->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for all clients",
@@ -1782,7 +1782,7 @@ static asio::awaitable<HandlerResult> S_64(std::shared_ptr<Client> c, Channel::M
auto s = c->require_server_state();
c->proxy_session->set_drop_mode(s, c->version(), c->override_random_seed, c->proxy_session->drop_mode);
if (!is_ep3(c->version()) && (c->proxy_session->lobby_mode != GameMode::CHALLENGE)) {
auto supermaps = s->supermaps_for_variations(
auto supermaps = s->data->supermaps_for_variations(
c->proxy_session->lobby_episode,
c->proxy_session->lobby_mode,
c->proxy_session->lobby_difficulty,
@@ -1976,8 +1976,8 @@ static asio::awaitable<HandlerResult> C_06(std::shared_ptr<Client> c, Channel::M
}
auto s = c->require_server_state();
char command_sentinel = s->chat_command_sentinel
? s->chat_command_sentinel
char command_sentinel = s->data->chat_command_sentinel
? s->data->chat_command_sentinel
: ((c->version() == Version::DC_11_2000) ? '@' : '$');
bool is_command = (text[0] == command_sentinel) ||
(text[0] == '\t' && text[1] != 'C' && text[2] == command_sentinel);
+8 -8
View File
@@ -25,14 +25,14 @@ void ProxySession::set_drop_mode(
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
this->item_creator = std::make_shared<ItemCreator>(
s->common_item_set(version, nullptr),
s->rare_item_set(version, nullptr),
s->armor_random_set,
s->tool_random_set,
s->weapon_random_set(this->lobby_difficulty),
s->tekker_adjustment_set,
s->item_parameter_table(version),
s->item_stack_limits(version),
s->data->common_item_set(version, nullptr),
s->data->rare_item_set(version, nullptr),
s->data->armor_random_set,
s->data->tool_random_set,
s->data->weapon_random_set(this->lobby_difficulty),
s->data->tekker_adjustment_set,
s->data->item_parameter_table(version),
s->data->item_stack_limits(version),
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
this->lobby_difficulty,
this->lobby_section_id,
+1 -1
View File
@@ -13,7 +13,7 @@
#include "Map.hh"
#include "SaveFileFormats.hh"
struct ServerState;
class ServerState;
struct ProxySession {
static size_t num_proxy_sessions;
+142 -141
View File
@@ -31,15 +31,15 @@ const char* ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME = "add_next_game_client";
asio::awaitable<void> on_connect(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
if (s->default_switch_assist_enabled) {
if (s->data->default_switch_assist_enabled) {
c->set_flag(Client::Flag::SWITCH_ASSIST_ENABLED);
}
switch (c->server_behavior) {
case ServerBehavior::PC_CONSOLE_DETECT: {
uint16_t pc_port = s->name_to_port_config.at("pc")->port;
uint16_t console_port = s->name_to_port_config.at("gc-us3")->port;
send_pc_console_split_reconnect(c, s->connect_address_for_client(c), pc_port, console_port);
uint16_t pc_port = s->data->name_to_port_config.at("pc").port;
uint16_t console_port = s->data->name_to_port_config.at("gc-us3").port;
send_pc_console_split_reconnect(c, s->data->connect_address_for_client(c), pc_port, console_port);
// TODO: There appears to be a bug that occurs rarely when a client connects to this port; sometimes it
// disconnects before receiving the data it needs. My hypothesis is that there's either a bug in Channel where
// the data isn't being sent before the RST, or there's a bug in AVE-TCP where it doesn't forward the last data
@@ -83,7 +83,7 @@ asio::awaitable<void> on_disconnect(std::shared_ptr<Client> c) {
static void send_main_menu(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
auto main_menu = std::make_shared<Menu>(MenuID::MAIN, s->name);
auto main_menu = std::make_shared<Menu>(MenuID::MAIN, s->data->name);
main_menu->items.emplace_back(
MainMenuItemID::GO_TO_LOBBY, "Go to lobby",
[wc = std::weak_ptr<Client>(c)]() -> std::string {
@@ -119,21 +119,21 @@ static void send_main_menu(std::shared_ptr<Client> c) {
"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DC_PROTOS | MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
uint32_t proxy_destinations_menu_item_flags =
(s->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
(s->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
(s->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) |
(s->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) |
(s->data->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
(s->data->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
(s->data->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) |
(s->data->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) |
MenuItem::Flag::INVISIBLE_ON_BB;
main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, "Proxy server",
"Connect to another\nserver through the\nproxy", proxy_destinations_menu_item_flags);
main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, "Download quests",
"Download quests", MenuItem::Flag::INVISIBLE_ON_DC_PROTOS | MenuItem::Flag::INVISIBLE_ON_PC_NTE | MenuItem::Flag::INVISIBLE_ON_BB);
if (!s->client_functions->patch_menu_empty(c->specific_version)) {
if (!s->data->client_functions->patch_menu_empty(c->specific_version)) {
main_menu->items.emplace_back(MainMenuItemID::PATCH_SWITCHES, "Patches",
"Change game\nbehaviors", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
}
if (!s->dol_file_index->empty()) {
if (!s->data->dol_file_index->empty()) {
main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, "Programs",
"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE | MenuItem::Flag::REQUIRES_SAVE_DISABLED);
}
@@ -147,7 +147,7 @@ static void send_main_menu(std::shared_ptr<Client> c) {
static void send_proxy_destinations_menu(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
send_menu(c, s->proxy_destinations_menu(c->version()));
send_menu(c, s->data->proxy_destinations_menu(c->version()));
}
static std::shared_ptr<const Menu> proxy_options_menu_for_client(std::shared_ptr<const Client> c) {
@@ -199,7 +199,7 @@ static std::shared_ptr<const Menu> proxy_options_menu_for_client(std::shared_ptr
add_flag_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, Client::Flag::SWITCH_ASSIST_ENABLED,
"Switch assist", "Automatically unlock\nmulti-player doors\nwhen you step on\nany of the door\'s\nswitches");
}
if ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) ||
if ((s->data->cheat_mode_behavior != DataIndex::BehaviorSwitch::OFF) ||
c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) {
if (!is_ep3(c->version())) {
add_flag_option(ProxyOptionsMenuItemID::INFINITE_HP, Client::Flag::INFINITE_HP_ENABLED,
@@ -217,7 +217,7 @@ static std::shared_ptr<const Menu> proxy_options_menu_for_client(std::shared_ptr
"Show whispers", "Show contents of\nwhisper messages\neven if they are not\nfor you");
}
}
if (s->proxy_allow_save_files) {
if (s->data->proxy_allow_save_files) {
add_flag_option(ProxyOptionsMenuItemID::SAVE_FILES, Client::Flag::PROXY_SAVE_FILES,
"Save files", "Save local copies of\nfiles from the\nremote server\n(quests, etc.)");
}
@@ -228,8 +228,8 @@ static asio::awaitable<void> send_auto_patches_if_needed(std::shared_ptr<Client>
auto s = c->require_server_state();
if (c->login->account->auto_patches_enabled.empty() &&
((c->version() != Version::BB_V4) || s->bb_required_patches.empty()) &&
s->auto_patches.empty()) {
((c->version() != Version::BB_V4) || s->data->bb_required_patches.empty()) &&
s->data->auto_patches.empty()) {
c->set_flag(Client::Flag::HAS_AUTO_PATCHES);
}
@@ -239,9 +239,9 @@ static asio::awaitable<void> send_auto_patches_if_needed(std::shared_ptr<Client>
std::unordered_set<std::shared_ptr<const ClientFunctionIndex::Function>> functions_to_send;
if (c->version() == Version::BB_V4) {
for (const auto& patch_name : s->bb_required_patches) {
for (const auto& patch_name : s->data->bb_required_patches) {
try {
functions_to_send.emplace(s->client_functions->get(patch_name, c->specific_version));
functions_to_send.emplace(s->data->client_functions->get(patch_name, c->specific_version));
} catch (const std::out_of_range&) {
std::string message = std::format(
"Your client is not compatible with a\nrequired patch on this server.\n\nClient version: {}\nPatch name: {}", str_for_specific_version(c->specific_version), patch_name);
@@ -251,9 +251,9 @@ static asio::awaitable<void> send_auto_patches_if_needed(std::shared_ptr<Client>
}
}
}
for (const auto& patch_name : s->auto_patches) {
for (const auto& patch_name : s->data->auto_patches) {
try {
functions_to_send.emplace(s->client_functions->get(patch_name, c->specific_version));
functions_to_send.emplace(s->data->client_functions->get(patch_name, c->specific_version));
} catch (const std::out_of_range&) {
c->log.warning_f("Server has auto patch {} enabled, but it is not available for specific_version {}",
patch_name, str_for_specific_version(c->specific_version));
@@ -261,7 +261,7 @@ static asio::awaitable<void> send_auto_patches_if_needed(std::shared_ptr<Client>
}
for (const auto& patch_name : c->login->account->auto_patches_enabled) {
try {
functions_to_send.emplace(s->client_functions->get(patch_name, c->specific_version));
functions_to_send.emplace(s->data->client_functions->get(patch_name, c->specific_version));
} catch (const std::out_of_range&) {
c->log.warning_f("Client has auto patch {} enabled, but it is not available for specific_version {}",
patch_name, str_for_specific_version(c->specific_version));
@@ -280,8 +280,8 @@ asio::awaitable<void> start_login_server_procedure(std::shared_ptr<Client> c) {
s->remove_client_from_lobby(c);
}
if (s->pre_lobby_event && (!is_ep3(c->version()) || s->ep3_menu_song < 0)) {
send_change_event(c, s->pre_lobby_event);
if (s->data->pre_lobby_event && (!is_ep3(c->version()) || s->data->ep3_menu_song < 0)) {
send_change_event(c, s->data->pre_lobby_event);
}
send_server_time(c);
@@ -303,7 +303,7 @@ asio::awaitable<void> start_login_server_procedure(std::shared_ptr<Client> c) {
c->set_flag(Client::Flag::HAS_EP3_CARD_DEFS);
}
if ((c->version() != Version::GC_EP3_NTE) && !c->check_flag(Client::Flag::HAS_EP3_MEDIA_UPDATES)) {
for (const auto& banner : s->ep3_lobby_banners) {
for (const auto& banner : s->data->ep3_lobby_banners) {
send_ep3_media_update(c, banner.type, banner.which, banner.data);
c->set_flag(Client::Flag::HAS_EP3_MEDIA_UPDATES);
}
@@ -317,13 +317,13 @@ asio::awaitable<void> start_login_server_procedure(std::shared_ptr<Client> c) {
c->set_flag(Client::Flag::LOADING);
c->log.info_f("LOADING flag set");
}
} else if (s->welcome_message.empty() ||
} else if (s->data->welcome_message.empty() ||
c->check_flag(Client::Flag::NO_D6) ||
!c->check_flag(Client::Flag::AT_WELCOME_MESSAGE)) {
c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE);
send_main_menu(c);
} else {
send_message_box(c, s->welcome_message);
send_message_box(c, s->data->welcome_message);
}
co_return;
}
@@ -342,7 +342,7 @@ static asio::awaitable<void> on_login_complete(std::shared_ptr<Client> c) {
!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE))) {
std::shared_ptr<const Quest> q;
try {
q = s->quest_index->get(s->enable_send_function_call_quest_numbers.at(c->specific_version));
q = s->data->quest_index->get(s->data->enable_send_function_call_quest_numbers.at(c->specific_version));
} catch (const std::out_of_range&) {
}
if (!q) {
@@ -430,7 +430,7 @@ asio::awaitable<void> start_proxy_session(std::shared_ptr<Client> c, const std::
if (!c->can_use_chat_commands()) {
c->clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED);
}
if (!s->proxy_allow_save_files) {
if (!s->data->proxy_allow_save_files) {
c->clear_flag(Client::Flag::PROXY_SAVE_FILES);
}
@@ -479,7 +479,7 @@ asio::awaitable<void> start_proxy_session(std::shared_ptr<Client> c, const std::
phosg::TerminalFormat::FG_YELLOW,
phosg::TerminalFormat::FG_RED,
false,
s->censor_credentials);
s->data->censor_credentials);
c->proxy_session = std::make_shared<ProxySession>(channel, pc);
if (c->version() == Version::GC_EP3) {
@@ -538,11 +538,11 @@ asio::awaitable<void> end_proxy_session(std::shared_ptr<Client> c, const std::st
}
if (is_in_game) {
std::string msg = std::format("You cannot return\nto $C6{}$C7\nwhile in a game.\n\n{}", s->name, error_message);
std::string msg = std::format("You cannot return\nto $C6{}$C7\nwhile in a game.\n\n{}", s->data->name, error_message);
send_ship_info(c, msg);
c->channel->disconnect();
} else {
std::string msg = std::format("You\'ve returned to\n$C6{}$C7\n\n{}", s->name, error_message);
std::string msg = std::format("You\'ve returned to\n$C6{}$C7\n\n{}", s->data->name, error_message);
send_ship_info(c, msg);
co_await start_login_server_procedure(c);
}
@@ -575,11 +575,11 @@ static asio::awaitable<void> on_04_U(std::shared_ptr<Client> c, Channel::Message
} catch (const AccountIndex::incorrect_password& e) {
result_code = 0x03;
} catch (const AccountIndex::missing_account& e) {
if (!s->allow_unregistered_users) {
if (!s->data->allow_unregistered_users) {
result_code = 0x08;
}
}
} else if (!c->username.empty() && !s->allow_unregistered_users) {
} else if (!c->username.empty() && !s->data->allow_unregistered_users) {
try {
s->account_index->from_bb_credentials(c->username, nullptr, false);
} catch (const AccountIndex::missing_account& e) {
@@ -593,19 +593,19 @@ static asio::awaitable<void> on_04_U(std::shared_ptr<Client> c, Channel::Message
}
// Switch to proxy session if there's a destination configured
if (is_patch(c->version()) && s->proxy_destination_patch.has_value()) {
const auto& [host, port] = *s->proxy_destination_patch;
if (is_patch(c->version()) && s->data->proxy_destination_patch.has_value()) {
const auto& [host, port] = *s->data->proxy_destination_patch;
co_await start_proxy_session(c, host, port, false);
} else {
// No proxy destination; continue with normal patch logic
bool is_bb = (c->version() == Version::BB_PATCH);
const std::string& message = is_bb ? s->bb_patch_server_message : s->pc_patch_server_message;
const std::string& message = is_bb ? s->data->bb_patch_server_message : s->data->pc_patch_server_message;
if (!message.empty()) {
send_message_box(c, message);
}
auto index = is_bb ? s->bb_patch_file_index : s->pc_patch_file_index;
auto index = is_bb ? s->data->bb_patch_file_index : s->data->pc_patch_file_index;
if (index.get()) {
c->channel->send(0x0B, 0x00); // Start patch session; go to root directory
@@ -812,7 +812,7 @@ static asio::awaitable<void> on_DB_GC(std::shared_ptr<Client> c, Channel::Messag
try {
auto s = c->require_server_state();
c->set_login(s->account_index->from_gc_credentials(
serial_number, c->access_key, &c->password, "", s->allow_unregistered_users));
serial_number, c->access_key, &c->password, "", s->data->allow_unregistered_users));
send_command(c, 0x9A, 0x02);
} catch (const AccountIndex::no_username& e) {
@@ -860,7 +860,7 @@ static asio::awaitable<void> on_88_DCNTE(std::shared_ptr<Client> c, Channel::Mes
try {
auto s = c->require_server_state();
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->allow_unregistered_users));
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->data->allow_unregistered_users));
send_command(c, 0x88, 0x00);
} catch (const AccountIndex::no_username& e) {
@@ -900,7 +900,7 @@ static asio::awaitable<void> on_8B_DCNTE(std::shared_ptr<Client> c, Channel::Mes
try {
auto s = c->require_server_state();
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->allow_unregistered_users));
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->data->allow_unregistered_users));
} catch (const AccountIndex::no_username& e) {
c->log.info_f("Login failed (no username)");
send_message_box(c, "Incorrect serial number");
@@ -944,10 +944,10 @@ static asio::awaitable<void> on_90_DC(std::shared_ptr<Client> c, Channel::Messag
try {
auto s = c->require_server_state();
if (c->serial_number.size() > 8 || c->access_key.size() > 8) {
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->allow_unregistered_users));
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->data->allow_unregistered_users));
} else {
serial_number = stoull(c->serial_number, nullptr, 16);
c->set_login(s->account_index->from_dc_credentials(serial_number, c->access_key, "", s->allow_unregistered_users));
c->set_login(s->account_index->from_dc_credentials(serial_number, c->access_key, "", s->data->allow_unregistered_users));
}
if (c->log.should_log(phosg::LogLevel::L_INFO)) {
c->log.info_f("Received login: {}", c->login->str());
@@ -1011,11 +1011,11 @@ static asio::awaitable<void> on_93_DC(std::shared_ptr<Client> c, Channel::Messag
uint32_t serial_number = 0;
try {
if (c->serial_number.size() > 8 || c->access_key.size() > 8) {
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->allow_unregistered_users));
c->set_login(s->account_index->from_dc_nte_credentials(c->serial_number, c->access_key, s->data->allow_unregistered_users));
} else {
serial_number = stoull(c->serial_number, nullptr, 16);
c->set_login(s->account_index->from_dc_credentials(
serial_number, c->access_key, c->login_character_name, s->allow_unregistered_users));
serial_number, c->access_key, c->login_character_name, s->data->allow_unregistered_users));
}
if (c->log.should_log(phosg::LogLevel::L_INFO)) {
c->log.info_f("Login: {}", c->login->str());
@@ -1079,7 +1079,7 @@ static asio::awaitable<void> on_9A(std::shared_ptr<Client> c, Channel::Message&
switch (c->version()) {
case Version::DC_V2: {
uint32_t serial_number = stoul(c->serial_number, nullptr, 16);
c->set_login(s->account_index->from_dc_credentials(serial_number, c->access_key, "", s->allow_unregistered_users));
c->set_login(s->account_index->from_dc_credentials(serial_number, c->access_key, "", s->data->allow_unregistered_users));
if (c->log.should_log(phosg::LogLevel::L_INFO)) {
c->log.info_f("Login: {}", c->login->str());
}
@@ -1098,10 +1098,10 @@ static asio::awaitable<void> on_9A(std::shared_ptr<Client> c, Channel::Message&
c->channel->version = Version::PC_NTE;
c->log.info_f("Changed client version to PC_NTE");
c->set_login(s->account_index->from_pc_nte_credentials(
cmd.guild_card_number, s->allow_unregistered_users && s->allow_pc_nte));
cmd.guild_card_number, s->data->allow_unregistered_users && s->data->allow_pc_nte));
} else {
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16);
c->set_login(s->account_index->from_pc_credentials(serial_number, c->access_key, "", s->allow_unregistered_users));
c->set_login(s->account_index->from_pc_credentials(serial_number, c->access_key, "", s->data->allow_unregistered_users));
}
break;
}
@@ -1253,7 +1253,7 @@ static asio::awaitable<void> on_9D_9E(std::shared_ptr<Client> c, Channel::Messag
case Version::DC_V2: {
uint32_t serial_number = stoul(c->serial_number, nullptr, 16);
c->set_login(s->account_index->from_dc_credentials(
serial_number, c->access_key, c->login_character_name, s->allow_unregistered_users));
serial_number, c->access_key, c->login_character_name, s->data->allow_unregistered_users));
break;
}
case Version::PC_NTE:
@@ -1268,11 +1268,11 @@ static asio::awaitable<void> on_9D_9E(std::shared_ptr<Client> c, Channel::Messag
c->channel->version = Version::PC_NTE;
c->log.info_f("Changed client version to PC_NTE");
c->set_login(s->account_index->from_pc_nte_credentials(
base_cmd->guild_card_number, s->allow_unregistered_users && s->allow_pc_nte));
base_cmd->guild_card_number, s->data->allow_unregistered_users && s->data->allow_pc_nte));
} else {
uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16);
c->set_login(s->account_index->from_pc_credentials(
serial_number, c->access_key, c->login_character_name, s->allow_unregistered_users));
serial_number, c->access_key, c->login_character_name, s->data->allow_unregistered_users));
}
break;
case Version::GC_NTE:
@@ -1314,7 +1314,7 @@ static asio::awaitable<void> on_9D_9E(std::shared_ptr<Client> c, Channel::Messag
// not; we'll call on_login_complete once we receive the B3 response
if (c->version() == Version::PC_V2) {
try {
auto code = s->client_functions->get("ReturnToken", SPECIFIC_VERSION_X86_INDETERMINATE);
auto code = s->data->client_functions->get("ReturnToken", SPECIFIC_VERSION_X86_INDETERMINATE);
std::unordered_map<std::string, uint32_t> label_writes{{"token", c->login->account->account_id}};
auto resp = co_await send_function_call(c, code, label_writes, nullptr, 0, 0x00400000, 0x0000E000, 0, true);
@@ -1374,7 +1374,7 @@ static asio::awaitable<void> on_9E_XB(std::shared_ptr<Client> c, Channel::Messag
uint64_t xb_user_id = stoull(c->access_key, nullptr, 16);
uint64_t xb_account_id = cmd.xb_netloc.account_id;
try {
c->set_login(s->account_index->from_xb_credentials(xb_gamertag, xb_user_id, xb_account_id, s->allow_unregistered_users));
c->set_login(s->account_index->from_xb_credentials(xb_gamertag, xb_user_id, xb_account_id, s->data->allow_unregistered_users));
} catch (const AccountIndex::no_username& e) {
c->log.info_f("Login failed (no username)");
send_command(c, 0x04, 0x03);
@@ -1420,7 +1420,7 @@ static asio::awaitable<void> on_93_BB(std::shared_ptr<Client> c, Channel::Messag
auto s = c->require_server_state();
try {
c->set_login(s->account_index->from_bb_credentials(c->username, &c->password, s->allow_unregistered_users));
c->set_login(s->account_index->from_bb_credentials(c->username, &c->password, s->data->allow_unregistered_users));
} catch (const AccountIndex::no_username& e) {
c->log.info_f("Login failed (no username)");
send_client_init_bb(c, 0x08);
@@ -1499,14 +1499,14 @@ static asio::awaitable<void> on_93_BB(std::shared_ptr<Client> c, Channel::Messag
// server phase, or else it won't know where to connect to during character selection. It's not clear why they
// didn't just make it use the initial connection address by default...
send_client_init_bb(c, 0);
send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at("bb-data1")->port);
send_reconnect(c, s->data->connect_address_for_client(c), s->data->name_to_port_config.at("bb-data1").port);
co_return;
} else if (s->proxy_destination_bb.has_value()) {
} else if (s->data->proxy_destination_bb.has_value()) {
// Start a proxy session immediately if there's a destination set. We don't send 00E6 (send_client_init_bb) in this
// case. This is because the login command is resent to the remote server, and we forward its response back to the
// client directly.
const auto& [host, port] = *s->proxy_destination_bb;
const auto& [host, port] = *s->data->proxy_destination_bb;
co_await start_proxy_session(c, host, port, c->bb_connection_phase != 0);
c->proxy_session->remote_client_config_data = c->bb_client_config;
co_return;
@@ -1520,7 +1520,7 @@ static asio::awaitable<void> on_93_BB(std::shared_ptr<Client> c, Channel::Messag
// ship select menu or a lobby join command.
co_await on_login_complete(c);
} else if (s->hide_download_commands) {
} else if (s->data->hide_download_commands) {
// The BB data server protocol is fairly well-understood and has some large commands, so we omit data logging for
// clients on the data server.
c->log.info_f("Client is in the BB data server phase; disabling command data logging for the rest of this client\'s session");
@@ -1547,8 +1547,8 @@ static asio::awaitable<void> on_B7_Ep3(std::shared_ptr<Client> c, Channel::Messa
// If the client is not in any lobby, assume they're at the main menu and send the menu song (if any).
auto s = c->require_server_state();
auto l = c->lobby.lock();
if (!l && (s->ep3_menu_song >= 0)) {
send_ep3_change_music(c->channel, s->ep3_menu_song);
if (!l && (s->data->ep3_menu_song >= 0)) {
send_ep3_change_music(c->channel, s->data->ep3_menu_song);
}
co_return;
}
@@ -1560,10 +1560,10 @@ static asio::awaitable<void> on_BA_Ep3(std::shared_ptr<Client> c, Channel::Messa
bool is_lobby = l && !l->is_game();
uint32_t current_meseta, total_meseta_earned;
if (s->ep3_infinite_meseta) {
if (s->data->ep3_infinite_meseta) {
current_meseta = 1000000;
total_meseta_earned = 1000000;
} else if (is_lobby && s->ep3_jukebox_is_free) {
} else if (is_lobby && s->data->ep3_jukebox_is_free) {
current_meseta = c->login->account->ep3_current_meseta;
total_meseta_earned = c->login->account->ep3_total_meseta_earned;
} else {
@@ -1622,7 +1622,7 @@ static bool add_next_game_client(std::shared_ptr<Lobby> l) {
state_cmd.state.first_team_turn = 0xFF;
state_cmd.state.tournament_flag = 0x01;
state_cmd.state.client_sc_card_types.clear(Episode3::CardType::INVALID_FF);
if ((c->version() != Version::GC_EP3_NTE) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if ((c->version() != Version::GC_EP3_NTE) && !(s->data->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&state_cmd, sizeof(state_cmd), mask_key);
}
@@ -1783,8 +1783,8 @@ static bool start_ep3_battle_table_game_if_ready(std::shared_ptr<Lobby> l, int16
}
game->tournament_match = tourn_match;
game->ep3_ex_result_values = (tourn_match && tourn && tourn->get_final_match() == tourn_match)
? s->ep3_tournament_final_round_ex_values
: s->ep3_tournament_ex_values;
? s->data->ep3_tournament_final_round_ex_values
: s->data->ep3_tournament_ex_values;
game->clients_to_add.clear();
for (const auto& it : game_clients) {
game->clients_to_add.emplace(it.first, it.second);
@@ -1956,8 +1956,8 @@ static asio::awaitable<void> on_CA_Ep3(std::shared_ptr<Client> c, Channel::Messa
if (!l->ep3_server || l->ep3_server->battle_finished) {
auto s = c->require_server_state();
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_RECORDING) {
l->battle_record = std::make_shared<Episode3::BattleRecord>(s->ep3_behavior_flags);
if (s->data->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_RECORDING) {
l->battle_record = std::make_shared<Episode3::BattleRecord>(s->data->ep3_behavior_flags);
for (auto existing_c : l->clients) {
if (existing_c) {
auto existing_p = existing_c->character_file();
@@ -2003,7 +2003,7 @@ static asio::awaitable<void> on_CA_Ep3(std::shared_ptr<Client> c, Channel::Messa
for (const auto& rc : rl->clients) {
if (rc) {
rc->ep3_prev_battle_record = l->battle_record;
if ((s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
if ((s->data->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
send_text_message(rc, "$C7Recording complete");
}
}
@@ -2037,13 +2037,13 @@ static asio::awaitable<void> on_CA_Ep3(std::shared_ptr<Client> c, Channel::Messa
uint32_t meseta_reward = 0;
auto& round_rewards = loser_team->has_any_human_players()
? s->ep3_defeat_player_meseta_rewards
: s->ep3_defeat_com_meseta_rewards;
? s->data->ep3_defeat_player_meseta_rewards
: s->data->ep3_defeat_com_meseta_rewards;
meseta_reward = (l->tournament_match->round_num - 1 < round_rewards.size())
? round_rewards[l->tournament_match->round_num - 1]
: round_rewards.back();
if (tourn && (l->tournament_match == tourn->get_final_match())) {
meseta_reward += s->ep3_final_round_meseta_bonus;
meseta_reward += s->data->ep3_final_round_meseta_bonus;
}
for (const auto& player : winner_team->players) {
if (player.is_human()) {
@@ -2116,7 +2116,7 @@ static asio::awaitable<void> on_D6_V3(std::shared_ptr<Client> c, Channel::Messag
check_size_v(msg.data.size(), 0);
if (c->check_flag(Client::Flag::IN_INFORMATION_MENU)) {
auto s = c->require_server_state();
send_menu(c, s->information_menu(c->version()));
send_menu(c, s->data->information_menu(c->version()));
} else if (c->check_flag(Client::Flag::AT_WELCOME_MESSAGE)) {
c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE);
send_main_menu(c);
@@ -2137,10 +2137,10 @@ static asio::awaitable<void> on_09(std::shared_ptr<Client> c, Channel::Message&
case MenuID::QUEST_EP1:
case MenuID::QUEST_EP2: {
bool is_download_quest = !c->lobby.lock();
if (!s->quest_index) {
if (!s->data->quest_index) {
send_quest_info(c, "$C7Quests are not available.", 0x00, is_download_quest);
} else {
auto q = s->quest_index->get(cmd.item_id);
auto q = s->data->quest_index->get(cmd.item_id);
if (!q) {
send_quest_info(c, "$C4Quest does not\nexist.", 0x00, is_download_quest);
} else {
@@ -2159,7 +2159,7 @@ static asio::awaitable<void> on_09(std::shared_ptr<Client> c, Channel::Message&
? Episode3::MapIndex::VisibilityFlag::ONLINE_TRIAL
: Episode3::MapIndex::VisibilityFlag::ONLINE_FINAL;
auto map = s->ep3_map_index->map_for_id(cmd.item_id);
auto map = s->data->ep3_map_index->map_for_id(cmd.item_id);
if (!map || !map->check_visibility_flag(vis_flag)) {
send_quest_info(c, "$C4Map does not exist.", 0x00, true);
} else {
@@ -2408,7 +2408,8 @@ static void on_quest_loaded(std::shared_ptr<Lobby> l) {
if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index);
}
lc->create_challenge_overlay(lc->version(), l->quest->meta.challenge_template_index, s->level_table(lc->version()));
lc->create_challenge_overlay(
lc->version(), l->quest->meta.challenge_template_index, s->data->level_table(lc->version()));
lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
@@ -2416,7 +2417,7 @@ static void on_quest_loaded(std::shared_ptr<Lobby> l) {
if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index);
}
lc->create_battle_overlay(l->quest->meta.battle_rules, s->level_table(lc->version()));
lc->create_battle_overlay(l->quest->meta.battle_rules, s->data->level_table(lc->version()));
lc->log.info_f("Created battle overlay");
}
}
@@ -2529,7 +2530,7 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
}
case MainMenuItemID::INFORMATION: {
send_menu(c, s->information_menu(c->version()));
send_menu(c, s->data->information_menu(c->version()));
c->set_flag(Client::Flag::IN_INFORMATION_MENU);
break;
}
@@ -2550,7 +2551,7 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
// We have to prepare the client for patches here, even though we don't send them from this mennu, because we
// need to know the client's specific_version before sending the menu.
co_await prepare_client_for_patches(c);
send_menu(c, c->require_server_state()->client_functions->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
send_menu(c, c->require_server_state()->data->client_functions->patch_switches_menu(c->specific_version, s->data->auto_patches, c->login->account->auto_patches_enabled));
break;
}
@@ -2559,7 +2560,7 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
throw std::runtime_error("client does not support send_function_call");
}
co_await prepare_client_for_patches(c);
send_menu(c, c->require_server_state()->dol_file_index->menu);
send_menu(c, c->require_server_state()->data->dol_file_index->menu);
break;
}
@@ -2572,7 +2573,7 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
break;
case MainMenuItemID::CLEAR_LICENSE: {
auto conf_menu = std::make_shared<Menu>(MenuID::CLEAR_LICENSE_CONFIRMATION, s->name);
auto conf_menu = std::make_shared<Menu>(MenuID::CLEAR_LICENSE_CONFIRMATION, s->data->name);
conf_menu->items.emplace_back(ClearLicenseConfirmationMenuItemID::CANCEL, "Go back",
"Go back to the\nmain menu", 0);
conf_menu->items.emplace_back(ClearLicenseConfirmationMenuItemID::CLEAR_LICENSE, "Clear license",
@@ -2607,7 +2608,7 @@ static void on_10_information(std::shared_ptr<Client> c, uint32_t item_id) {
send_main_menu(c);
} else {
try {
auto contents = c->require_server_state()->information_contents_for_client(c);
auto contents = c->require_server_state()->data->information_contents_for_client(c);
send_message_box(c, contents->at(item_id));
} catch (const std::out_of_range&) {
send_message_box(c, "$C6No such information exists.");
@@ -2694,7 +2695,7 @@ static asio::awaitable<void> on_10_proxy_destinations(std::shared_ptr<Client> c,
auto s = c->require_server_state();
const std::pair<std::string, uint16_t>* dest = nullptr;
try {
dest = &s->proxy_destinations(c->version()).at(item_id);
dest = &s->data->proxy_destinations(c->version()).at(item_id);
} catch (const std::out_of_range&) {
}
@@ -2784,7 +2785,7 @@ static void on_10_game_menu(std::shared_ptr<Client> c, uint32_t item_id, const s
static void on_10_quest_categories(std::shared_ptr<Client> c, uint32_t item_id) {
if (is_ep3(c->version())) {
auto s = c->require_server_state();
if (!s->ep3_map_index) {
if (!s->data->ep3_map_index) {
send_lobby_message_box(c, "$C7Quests are not available.");
return;
}
@@ -2792,7 +2793,7 @@ static void on_10_quest_categories(std::shared_ptr<Client> c, uint32_t item_id)
} else {
auto s = c->require_server_state();
if (!s->quest_index) {
if (!s->data->quest_index) {
send_lobby_message_box(c, "$C7Quests are not available.");
return;
}
@@ -2805,7 +2806,7 @@ static void on_10_quest_categories(std::shared_ptr<Client> c, uint32_t item_id)
include_condition = l->quest_include_condition();
}
const auto& quests = s->quest_index->filter(episode, version_flags, item_id, include_condition);
const auto& quests = s->data->quest_index->filter(episode, version_flags, item_id, include_condition);
send_quest_menu(c, quests, !l);
}
}
@@ -2816,11 +2817,11 @@ static void on_10_quest_menu(std::shared_ptr<Client> c, uint32_t item_id) {
}
auto s = c->require_server_state();
if (!s->quest_index) {
if (!s->data->quest_index) {
send_lobby_message_box(c, "$C7Quests are not\navailable.");
return;
}
auto q = s->quest_index->get(item_id);
auto q = s->data->quest_index->get(item_id);
if (!q) {
send_lobby_message_box(c, "$C7Quest does not exist.");
return;
@@ -2874,7 +2875,7 @@ static void on_10_ep3_download_quest_menu(std::shared_ptr<Client> c, uint32_t it
throw std::runtime_error("Episode 3 quests can only be downloaded when client is not in a lobby");
}
auto map = s->ep3_map_index->map_for_id(item_id);
auto map = s->data->ep3_map_index->map_for_id(item_id);
auto vis_flag = (c->version() == Version::GC_EP3_NTE)
? Episode3::MapIndex::VisibilityFlag::ONLINE_TRIAL
@@ -2900,12 +2901,12 @@ static void on_10_patch_switches(std::shared_ptr<Client> c, uint32_t item_id) {
}
auto s = c->require_server_state();
auto fn = s->client_functions->get_by_menu_item_id(item_id);
auto fn = s->data->client_functions->get_by_menu_item_id(item_id);
if (!c->login->account->auto_patches_enabled.emplace(fn->short_name).second) {
c->login->account->auto_patches_enabled.erase(fn->short_name);
}
c->login->account->save();
send_menu(c, s->client_functions->patch_switches_menu(c->specific_version, s->auto_patches, c->login->account->auto_patches_enabled));
send_menu(c, s->data->client_functions->patch_switches_menu(c->specific_version, s->data->auto_patches, c->login->account->auto_patches_enabled));
}
}
@@ -2919,7 +2920,7 @@ static asio::awaitable<void> on_10_programs(std::shared_ptr<Client> c, uint32_t
}
auto s = c->require_server_state();
auto dol = s->dol_file_index->item_id_to_file.at(item_id);
auto dol = s->data->dol_file_index->item_id_to_file.at(item_id);
co_await send_dol_file(c, dol); // Disconnects the client
}
}
@@ -3119,7 +3120,7 @@ static asio::awaitable<void> on_08_E6(std::shared_ptr<Client> c, Channel::Messag
static asio::awaitable<void> on_1F(std::shared_ptr<Client> c, Channel::Message& msg) {
check_size_v(msg.data.size(), 0);
auto s = c->require_server_state();
send_menu(c, s->information_menu(c->version()), true);
send_menu(c, s->data->information_menu(c->version()), true);
co_return;
}
@@ -3652,8 +3653,8 @@ static asio::awaitable<void> on_06(std::shared_ptr<Client> c, Channel::Message&
}
auto s = c->require_server_state();
char command_sentinel = s->chat_command_sentinel
? s->chat_command_sentinel
char command_sentinel = s->data->chat_command_sentinel
? s->data->chat_command_sentinel
: ((c->version() == Version::DC_11_2000) ? '@' : '$');
if ((text[0] == command_sentinel) && c->can_use_chat_commands()) {
if (text[1] == command_sentinel) {
@@ -3970,7 +3971,7 @@ static asio::awaitable<void> on_E5_BB(std::shared_ptr<Client> c, Channel::Messag
try {
auto s = c->require_server_state();
c->create_character_file(
c->login->account->account_id, c->language(), cmd.preview.visual, s->level_table(c->version()));
c->login->account->account_id, c->language(), cmd.preview.visual, s->data->level_table(c->version()));
} catch (const std::exception& e) {
send_message_box(c, std::format("$C6New character could not be created:\n{}", e.what()));
should_send_approve = false;
@@ -4103,7 +4104,7 @@ static asio::awaitable<void> on_DF_BB(std::shared_ptr<Client> c, Channel::Messag
if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index);
}
lc->create_challenge_overlay(lc->version(), l->quest->meta.challenge_template_index, s->level_table(lc->version()));
lc->create_challenge_overlay(lc->version(), l->quest->meta.challenge_template_index, s->data->level_table(lc->version()));
lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
}
@@ -4169,10 +4170,10 @@ static asio::awaitable<void> on_DF_BB(std::shared_ptr<Client> c, Channel::Messag
? p->challenge_records.ep2_online_award_state
: p->challenge_records.ep1_online_award_state;
award_state.rank_award_flags |= cmd.rank_bitmask;
p->add_item(cmd.item, *s->item_stack_limits(c->version()));
p->add_item(cmd.item, *s->data->item_stack_limits(c->version()));
l->on_item_id_generated_externally(cmd.item.id);
l->log.info_f("(Challenge mode) Item awarded to player {}: {}",
c->lobby_client_id, s->describe_item(Version::BB_V4, cmd.item));
c->lobby_client_id, s->data->describe_item(Version::BB_V4, cmd.item));
break;
}
}
@@ -4280,18 +4281,18 @@ static void on_choice_search_t(std::shared_ptr<Client> c, const ChoiceSearchConf
result.info_string.encode(info_string, c->language());
std::string location_string;
if (l->is_game()) {
location_string = std::format("{},,BLOCK01,{}", l->name, s->name);
location_string = std::format("{},,BLOCK01,{}", l->name, s->data->name);
} else if (l->is_ep3()) {
location_string = std::format("BLOCK01-C{:02},,BLOCK01,{}", l->lobby_id - 15, s->name);
location_string = std::format("BLOCK01-C{:02},,BLOCK01,{}", l->lobby_id - 15, s->data->name);
} else {
location_string = std::format("BLOCK01-{:02},,BLOCK01,{}", l->lobby_id, s->name);
location_string = std::format("BLOCK01-{:02},,BLOCK01,{}", l->lobby_id, s->data->name);
}
result.location_string.encode(location_string, c->language());
result.reconnect_command_header.command = 0x19;
result.reconnect_command_header.flag = 0x00;
result.reconnect_command_header.size = sizeof(result.reconnect_command) + sizeof(result.reconnect_command_header);
result.reconnect_command.address = s->connect_address_for_client(c);
result.reconnect_command.port = s->game_server_port_for_version(c->version());
result.reconnect_command.address = s->data->connect_address_for_client(c);
result.reconnect_command.port = s->data->game_server_port_for_version(c->version());
result.meet_user.lobby_refs[0].menu_id = MenuID::LOBBY;
result.meet_user.lobby_refs[0].item_id = l->lobby_id;
result.meet_user.player_name.encode(lp->disp.visual.name.decode(lc->language()), c->language());
@@ -4508,7 +4509,7 @@ std::shared_ptr<Lobby> create_game_generic(
auto current_lobby = creator_c->require_lobby();
size_t min_level = s->default_min_level_for_game(creator_c->version(), episode, difficulty);
size_t min_level = s->data->default_min_level_for_game(creator_c->version(), episode, difficulty);
auto p = creator_c->character_file();
if (!creator_c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
@@ -4524,7 +4525,7 @@ std::shared_ptr<Lobby> create_game_generic(
game->episode = episode;
game->mode = mode;
game->difficulty = difficulty;
game->allowed_versions = s->compatibility_groups.at(static_cast<size_t>(creator_c->version()));
game->allowed_versions = s->data->compatibility_groups.at(static_cast<size_t>(creator_c->version()));
static_assert(NUM_VERSIONS == 14, "Don't forget to update the group compatibility restrictions");
if (!allow_v1 || (difficulty == Difficulty::ULTIMATE) || (mode == GameMode::CHALLENGE) || (mode == GameMode::SOLO)) {
game->forbid_version(Version::DC_NTE);
@@ -4579,13 +4580,13 @@ std::shared_ptr<Lobby> create_game_generic(
game->floor_item_managers.emplace_back(game->lobby_id, game->floor_item_managers.size());
}
if (s->behavior_enabled(s->cheat_mode_behavior)) {
if (s->data->behavior_enabled(s->data->cheat_mode_behavior)) {
game->set_flag(Lobby::Flag::CHEATS_ENABLED);
}
if (!s->behavior_can_be_overridden(s->cheat_mode_behavior)) {
if (!s->data->behavior_can_be_overridden(s->data->cheat_mode_behavior)) {
game->set_flag(Lobby::Flag::CANNOT_CHANGE_CHEAT_MODE);
}
if (s->use_game_creator_section_id) {
if (s->data->use_game_creator_section_id) {
game->set_flag(Lobby::Flag::USE_CREATOR_SECTION_ID);
}
if (watched_lobby || battle_player) {
@@ -4616,8 +4617,8 @@ std::shared_ptr<Lobby> create_game_generic(
game->battle_player = battle_player;
battle_player->set_lobby(game);
}
game->base_exp_multiplier = s->bb_global_exp_multiplier;
game->exp_share_multiplier = s->exp_share_multiplier;
game->base_exp_multiplier = s->data->bb_global_exp_multiplier;
game->exp_share_multiplier = s->data->exp_share_multiplier;
const std::unordered_map<uint16_t, IntegralExpression>* quest_flag_rewrites;
switch (creator_c->version()) {
@@ -4627,31 +4628,31 @@ std::shared_ptr<Lobby> create_game_generic(
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
quest_flag_rewrites = &s->quest_flag_rewrites_v1_v2;
quest_flag_rewrites = &s->data->quest_flag_rewrites_v1_v2;
if (game->mode == GameMode::BATTLE) {
game->drop_mode = s->default_drop_mode_v1_v2_battle;
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_battle;
game->drop_mode = s->data->default_drop_mode_v1_v2_battle;
game->allowed_drop_modes = s->data->allowed_drop_modes_v1_v2_battle;
} else if (game->mode == GameMode::CHALLENGE) {
game->drop_mode = s->default_drop_mode_v1_v2_challenge;
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_challenge;
game->drop_mode = s->data->default_drop_mode_v1_v2_challenge;
game->allowed_drop_modes = s->data->allowed_drop_modes_v1_v2_challenge;
} else {
game->drop_mode = s->default_drop_mode_v1_v2_normal;
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_normal;
game->drop_mode = s->data->default_drop_mode_v1_v2_normal;
game->allowed_drop_modes = s->data->allowed_drop_modes_v1_v2_normal;
}
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
quest_flag_rewrites = &s->quest_flag_rewrites_v3;
quest_flag_rewrites = &s->data->quest_flag_rewrites_v3;
if (game->mode == GameMode::BATTLE) {
game->drop_mode = s->default_drop_mode_v3_battle;
game->allowed_drop_modes = s->allowed_drop_modes_v3_battle;
game->drop_mode = s->data->default_drop_mode_v3_battle;
game->allowed_drop_modes = s->data->allowed_drop_modes_v3_battle;
} else if (game->mode == GameMode::CHALLENGE) {
game->drop_mode = s->default_drop_mode_v3_challenge;
game->allowed_drop_modes = s->allowed_drop_modes_v3_challenge;
game->drop_mode = s->data->default_drop_mode_v3_challenge;
game->allowed_drop_modes = s->data->allowed_drop_modes_v3_challenge;
} else {
game->drop_mode = s->default_drop_mode_v3_normal;
game->allowed_drop_modes = s->allowed_drop_modes_v3_normal;
game->drop_mode = s->data->default_drop_mode_v3_normal;
game->allowed_drop_modes = s->data->allowed_drop_modes_v3_normal;
}
break;
case Version::GC_EP3_NTE:
@@ -4661,16 +4662,16 @@ std::shared_ptr<Lobby> create_game_generic(
game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
break;
case Version::BB_V4:
quest_flag_rewrites = &s->quest_flag_rewrites_v4;
quest_flag_rewrites = &s->data->quest_flag_rewrites_v4;
if (game->mode == GameMode::BATTLE) {
game->drop_mode = s->default_drop_mode_v4_battle;
game->allowed_drop_modes = s->allowed_drop_modes_v4_battle;
game->drop_mode = s->data->default_drop_mode_v4_battle;
game->allowed_drop_modes = s->data->allowed_drop_modes_v4_battle;
} else if (game->mode == GameMode::CHALLENGE) {
game->drop_mode = s->default_drop_mode_v4_challenge;
game->allowed_drop_modes = s->allowed_drop_modes_v4_challenge;
game->drop_mode = s->data->default_drop_mode_v4_challenge;
game->allowed_drop_modes = s->data->allowed_drop_modes_v4_challenge;
} else {
game->drop_mode = s->default_drop_mode_v4_normal;
game->allowed_drop_modes = s->allowed_drop_modes_v4_normal;
game->drop_mode = s->data->default_drop_mode_v4_normal;
game->allowed_drop_modes = s->data->allowed_drop_modes_v4_normal;
}
// Disallow CLIENT mode on BB
if (game->drop_mode == ServerDropMode::CLIENT) {
@@ -4698,9 +4699,9 @@ std::shared_ptr<Lobby> create_game_generic(
bool is_solo = (game->mode == GameMode::SOLO);
if (game->mode == GameMode::CHALLENGE) {
game->rare_enemy_rates = s->rare_enemy_rates_challenge;
game->rare_enemy_rates = s->data->rare_enemy_rates_challenge;
} else {
game->rare_enemy_rates = s->rare_enemy_rates(game->difficulty);
game->rare_enemy_rates = s->data->rare_enemy_rates(game->difficulty);
}
if (game->episode != Episode::EP3) {
@@ -4714,7 +4715,7 @@ std::shared_ptr<Lobby> create_game_generic(
auto vars_str = game->variations.str();
game->log.info_f("Using variations from client override: {}", vars_str);
} else {
auto sdt = s->set_data_table(creator_c->version(), game->episode, game->mode, game->difficulty);
auto sdt = s->data->set_data_table(creator_c->version(), game->episode, game->mode, game->difficulty);
game->variations = sdt->generate_variations(game->episode, is_solo, game->rand_crypt);
auto vars_str = game->variations.str();
game->log.info_f("Using random variations: {}", vars_str);
@@ -4850,7 +4851,7 @@ static asio::awaitable<void> on_0C_C1_E7_EC(std::shared_ptr<Client> c, Channel::
game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), episode, mode, cmd.difficulty, allow_v1, watched_lobby);
if (game && (game->episode == Episode::EP3)) {
game->ep3_ex_result_values = s->ep3_default_ex_values;
game->ep3_ex_result_values = s->data->ep3_default_ex_values;
if (spectators_forbidden) {
game->set_flag(Lobby::Flag::SPECTATORS_FORBIDDEN);
}
@@ -4954,8 +4955,8 @@ static asio::awaitable<void> on_6F(std::shared_ptr<Client> c, Channel::Message&
auto s = c->require_server_state();
std::shared_ptr<const Quest> q;
try {
int64_t quest_num = s->enable_send_function_call_quest_numbers.at(c->specific_version);
q = s->quest_index->get(quest_num);
int64_t quest_num = s->data->enable_send_function_call_quest_numbers.at(c->specific_version);
q = s->data->quest_index->get(quest_num);
} catch (const std::out_of_range&) {
throw std::logic_error("cannot find patch enable quest after it was previously found during login");
}
@@ -5186,8 +5187,8 @@ static asio::awaitable<void> on_D2_V3_BB(std::shared_ptr<Client> c, Channel::Mes
// Delete items that are being given away
for (const auto& item : c->pending_item_trade->items) {
size_t amount = item.stack_size(*s->item_stack_limits(c->version()));
p->remove_item(item.id, amount, *s->item_stack_limits(c->version()));
size_t amount = item.stack_size(*s->data->item_stack_limits(c->version()));
p->remove_item(item.id, amount, *s->data->item_stack_limits(c->version()));
// This is a special case: when the trade is executed, the client deletes the traded items from its own
// inventory automatically, so we should NOT send the 6x29 to that client; we should only send it to the other
@@ -5203,7 +5204,7 @@ static asio::awaitable<void> on_D2_V3_BB(std::shared_ptr<Client> c, Channel::Mes
for (const auto& trade_item : other_c->pending_item_trade->items) {
ItemData added_item = trade_item;
added_item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(added_item, *s->item_stack_limits(c->version()));
p->add_item(added_item, *s->data->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, added_item);
}
send_command(c, 0xD3, 0x00);
@@ -5576,7 +5577,7 @@ static asio::awaitable<void> on_EA_BB(std::shared_ptr<Client> c, Channel::Messag
send_team_metadata_change_notifications(s, team, 0, TeamMetadataChange::REWARD_FLAGS);
}
if (!reward.reward_item.empty()) {
c->bank_file()->add_item(reward.reward_item, *s->item_stack_limits(c->version()));
c->bank_file()->add_item(reward.reward_item, *s->data->item_stack_limits(c->version()));
c->print_bank();
}
}
+104 -109
View File
@@ -333,7 +333,7 @@ void forward_subcommand_with_item_transcode_t(std::shared_ptr<Client> c, uint8_t
out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), out_cmd.header.subcommand);
if (out_cmd.header.subcommand) {
out_cmd.item_data.decode_for_version(c->version());
out_cmd.item_data.encode_for_version(lc->version(), s->item_parameter_table_for_encode(lc->version()));
out_cmd.item_data.encode_for_version(lc->version(), s->data->item_parameter_table_for_encode(lc->version()));
send_command_t(lc, command, flag, out_cmd);
} else {
lc->log.info_f("Subcommand cannot be translated to client\'s version");
@@ -937,7 +937,7 @@ G_SyncPlayerDispAndInventory_DCNTE_6x70 Parsed6x70Data::as_dc_nte(std::shared_pt
ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh;
ret.visual.enforce_lobby_join_limits_for_version(Version::DC_NTE);
uint32_t name_color = s->name_color_for_client(this->from_version, this->from_client_customization);
uint32_t name_color = s->data->name_color_for_client(this->from_version, this->from_client_customization);
if (name_color) {
ret.visual.sh.name_color = name_color;
ret.visual.sh.compute_name_color_checksum();
@@ -950,7 +950,7 @@ G_SyncPlayerDispAndInventory_DCNTE_6x70 Parsed6x70Data::as_dc_nte(std::shared_pt
ret.num_items,
this->item_version,
Version::DC_NTE,
s->item_parameter_table_for_encode(Version::DC_NTE));
s->data->item_parameter_table_for_encode(Version::DC_NTE));
return ret;
}
@@ -967,7 +967,7 @@ G_SyncPlayerDispAndInventory_DC112000_6x70 Parsed6x70Data::as_dc_112000(std::sha
ret.player_flags = this->get_player_flags(false);
ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh;
uint32_t name_color = s->name_color_for_client(this->from_version, this->from_client_customization);
uint32_t name_color = s->data->name_color_for_client(this->from_version, this->from_client_customization);
if (name_color) {
ret.visual.sh.name_color = name_color;
ret.visual.sh.compute_name_color_checksum();
@@ -980,7 +980,7 @@ G_SyncPlayerDispAndInventory_DC112000_6x70 Parsed6x70Data::as_dc_112000(std::sha
ret.num_items,
this->item_version,
Version::DC_11_2000,
s->item_parameter_table_for_encode(Version::DC_11_2000));
s->data->item_parameter_table_for_encode(Version::DC_11_2000));
ret.visual.enforce_lobby_join_limits_for_version(Version::DC_11_2000);
return ret;
}
@@ -990,7 +990,7 @@ G_SyncPlayerDispAndInventory_DC_PC_6x70 Parsed6x70Data::as_dc_pc(std::shared_ptr
ret.base = this->base_v1(false);
ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh;
uint32_t name_color = s->name_color_for_client(this->from_version, this->from_client_customization);
uint32_t name_color = s->data->name_color_for_client(this->from_version, this->from_client_customization);
if (name_color) {
ret.visual.sh.name_color = name_color;
ret.visual.sh.compute_name_color_checksum();
@@ -999,7 +999,7 @@ G_SyncPlayerDispAndInventory_DC_PC_6x70 Parsed6x70Data::as_dc_pc(std::shared_ptr
ret.num_items = this->num_items;
ret.items = this->items;
transcode_inventory_items(
ret.items, ret.num_items, this->item_version, to_version, s->item_parameter_table_for_encode(to_version));
ret.items, ret.num_items, this->item_version, to_version, s->data->item_parameter_table_for_encode(to_version));
ret.visual.sh.enforce_lobby_join_limits_for_version(to_version);
return ret;
}
@@ -1010,7 +1010,7 @@ G_SyncPlayerDispAndInventory_GC_6x70 Parsed6x70Data::as_gc_gcnte(std::shared_ptr
ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh;
ret.visual.enforce_lobby_join_limits_for_version(to_version);
uint32_t name_color = s->name_color_for_client(this->from_version, this->from_client_customization);
uint32_t name_color = s->data->name_color_for_client(this->from_version, this->from_client_customization);
if (name_color) {
ret.visual.sh.name_color = name_color;
if (is_v1_or_v2(to_version)) {
@@ -1023,7 +1023,7 @@ G_SyncPlayerDispAndInventory_GC_6x70 Parsed6x70Data::as_gc_gcnte(std::shared_ptr
ret.num_items = this->num_items;
ret.items = this->items;
transcode_inventory_items(
ret.items, ret.num_items, this->item_version, to_version, s->item_parameter_table_for_encode(to_version));
ret.items, ret.num_items, this->item_version, to_version, s->data->item_parameter_table_for_encode(to_version));
ret.floor = this->floor;
return ret;
}
@@ -1033,7 +1033,7 @@ G_SyncPlayerDispAndInventory_XB_6x70 Parsed6x70Data::as_xb(std::shared_ptr<Serve
ret.base = this->base_v1(true);
ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh;
uint32_t name_color = s->name_color_for_client(this->from_version, this->from_client_customization);
uint32_t name_color = s->data->name_color_for_client(this->from_version, this->from_client_customization);
if (name_color) {
ret.visual.sh.name_color = name_color;
ret.visual.sh.name_color_checksum = 0;
@@ -1042,7 +1042,7 @@ G_SyncPlayerDispAndInventory_XB_6x70 Parsed6x70Data::as_xb(std::shared_ptr<Serve
ret.num_items = this->num_items;
ret.items = this->items;
transcode_inventory_items(
ret.items, ret.num_items, this->item_version, Version::XB_V3, s->item_parameter_table_for_encode(Version::XB_V3));
ret.items, ret.num_items, this->item_version, Version::XB_V3, s->data->item_parameter_table_for_encode(Version::XB_V3));
ret.visual.sh.enforce_lobby_join_limits_for_version(Version::XB_V3);
ret.floor = this->floor;
ret.xb_user_id_high = this->xb_user_id >> 32;
@@ -1058,7 +1058,7 @@ G_SyncPlayerDispAndInventory_BB_6x70 Parsed6x70Data::as_bb(std::shared_ptr<Serve
ret.visual.sh = this->visual_sh;
ret.visual.name.encode(this->name, this->language);
ret.visual.enforce_lobby_join_limits_for_version(Version::BB_V4);
uint32_t name_color = s->name_color_for_client(this->from_version, this->from_client_customization);
uint32_t name_color = s->data->name_color_for_client(this->from_version, this->from_client_customization);
if (name_color) {
ret.visual.sh.name_color = name_color;
ret.visual.sh.name_color_checksum = 0;
@@ -1067,7 +1067,7 @@ G_SyncPlayerDispAndInventory_BB_6x70 Parsed6x70Data::as_bb(std::shared_ptr<Serve
ret.num_items = this->num_items;
ret.items = this->items;
transcode_inventory_items(
ret.items, ret.num_items, this->item_version, Version::BB_V4, s->item_parameter_table_for_encode(Version::BB_V4));
ret.items, ret.num_items, this->item_version, Version::BB_V4, s->data->item_parameter_table_for_encode(Version::BB_V4));
ret.floor = this->floor;
ret.xb_user_id_high = this->xb_user_id >> 32;
ret.xb_user_id_low = this->xb_user_id;
@@ -1389,7 +1389,8 @@ static void on_ep3_battle_subs(std::shared_ptr<Client> c, SubcommandMessage& msg
if (!lc || (lc == c)) {
continue;
}
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING) && (lc->version() != Version::GC_EP3_NTE)) {
if (!(s->data->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING) &&
(lc->version() != Version::GC_EP3_NTE)) {
set_mask_for_ep3_game_command(msg.data, msg.size, (phosg::random_object<uint32_t>() % 0xFF) + 1);
}
send_command(lc, 0xC9, 0x00, msg.data, msg.size);
@@ -1531,12 +1532,12 @@ static void on_word_select_t(std::shared_ptr<Client> c, SubcommandMessage& msg)
if (is_big_endian(lc->version())) {
G_WordSelectBE_6x74 out_cmd = {
subcommand, cmd.size, cmd.client_id.load(),
s->word_select_table->translate(cmd.message, from_version, lc_version)};
s->data->word_select_table->translate(cmd.message, from_version, lc_version)};
send_command_t(lc, 0x60, 0x00, out_cmd);
} else {
G_WordSelect_6x74 out_cmd = {
subcommand, cmd.size, cmd.client_id.load(),
s->word_select_table->translate(cmd.message, from_version, lc_version)};
s->data->word_select_table->translate(cmd.message, from_version, lc_version)};
send_command_t(lc, 0x60, 0x00, out_cmd);
}
@@ -1933,12 +1934,12 @@ static void on_player_drop_item(std::shared_ptr<Client> c, SubcommandMessage& ms
auto s = c->require_server_state();
auto l = c->require_lobby();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, 0, *s->item_stack_limits(c->version()));
auto item = p->remove_item(cmd.item_id, 0, *s->data->item_stack_limits(c->version()));
l->add_item(cmd.floor, item, cmd.pos, nullptr, nullptr, 0x00F);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} dropped item {:08X} ({}) at {}:({:g}, {:g})",
cmd.header.client_id, cmd.item_id, name, cmd.floor, cmd.pos.x, cmd.pos.z);
c->print_inventory();
@@ -1973,15 +1974,15 @@ static void on_create_inventory_item_t(std::shared_ptr<Client> c, SubcommandMess
}
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} created inventory item {:08X} ({}) in inventory of NPC {:02X}; ignoring", c->lobby_client_id, item.id, name, cmd.header.client_id);
}
} else {
c->character_file()->add_item(item, *s->item_stack_limits(c->version()));
c->character_file()->add_item(item, *s->data->item_stack_limits(c->version()));
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} created inventory item {:08X} ({})", c->lobby_client_id, item.id, name);
c->print_inventory();
}
@@ -2022,7 +2023,7 @@ static void on_drop_partial_stack_t(std::shared_ptr<Client> c, SubcommandMessage
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} split stack to create floor item {:08X} ({}) at {}:({:g},{:g})",
cmd.header.client_id, item.id, name, cmd.floor, cmd.pos.x, cmd.pos.z);
c->print_inventory();
@@ -2056,7 +2057,7 @@ static void on_drop_partial_stack_bb(std::shared_ptr<Client> c, SubcommandMessag
auto s = c->require_server_state();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
// If a stack was split, the original item still exists, so the dropped item needs a new ID. remove_item signals this
@@ -2074,7 +2075,7 @@ static void on_drop_partial_stack_bb(std::shared_ptr<Client> c, SubcommandMessag
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} split stack {:08X} (removed: {}) at {}:({:g}, {:g})",
cmd.header.client_id, cmd.item_id, name, cmd.floor, cmd.pos.x, cmd.pos.z);
c->print_inventory();
@@ -2100,13 +2101,13 @@ static void on_buy_shop_item(std::shared_ptr<Client> c, SubcommandMessage& msg)
item.data2d = 0; // Clear the price field
item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id);
p->add_item(item, *s->item_stack_limits(c->version()));
p->add_item(item, *s->data->item_stack_limits(c->version()));
size_t price = s->item_parameter_table(c->version())->price_for_item(item);
size_t price = s->data->item_parameter_table(c->version())->price_for_item(item);
p->remove_meseta(price, c->version() != Version::BB_V4);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} bought item {:08X} ({}) from shop ({} Meseta)",
cmd.header.client_id, item.id, name, price);
c->print_inventory();
@@ -2125,7 +2126,7 @@ void send_item_notification_if_needed(std::shared_ptr<Client> c, const ItemData&
break;
case Client::ItemDropNotificationMode::RARES_ONLY:
should_notify = (is_from_rare_table || (item.data1[0] == 0x03)) &&
s->item_parameter_table(c->version())->is_item_rare(item);
s->data->item_parameter_table(c->version())->is_item_rare(item);
should_include_rare_header = true;
break;
case Client::ItemDropNotificationMode::ALL_ITEMS:
@@ -2137,7 +2138,7 @@ void send_item_notification_if_needed(std::shared_ptr<Client> c, const ItemData&
}
if (should_notify) {
std::string name = s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
std::string name = s->data->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
const char* rare_header = (should_include_rare_header ? "$C6Rare item dropped:\n" : "");
send_text_message_fmt(c, "{}{}", rare_header, name);
}
@@ -2161,7 +2162,7 @@ static void on_box_or_enemy_item_drop_t(std::shared_ptr<Client> c, SubcommandMes
throw std::runtime_error("BB client sent 6x5F command");
}
bool should_notify = s->rare_notifs_enabled_for_client_drops && (l->drop_mode == ServerDropMode::CLIENT);
bool should_notify = s->data->rare_notifs_enabled_for_client_drops && (l->drop_mode == ServerDropMode::CLIENT);
std::shared_ptr<const MapState::EnemyState> ene_st;
std::shared_ptr<const MapState::ObjectState> obj_st;
@@ -2179,15 +2180,9 @@ static void on_box_or_enemy_item_drop_t(std::shared_ptr<Client> c, SubcommandMes
l->on_item_id_generated_externally(item.id);
l->add_item(cmd.item.floor, item, cmd.item.pos, obj_st, ene_st, should_notify ? 0x100F : 0x000F);
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} (leader) created floor item {:08X} ({}){} at {}:({:g}, {:g})",
l->leader_id,
item.id,
name,
from_entity_str,
cmd.item.floor,
cmd.item.pos.x,
cmd.item.pos.z);
l->leader_id, item.id, name, from_entity_str, cmd.item.floor, cmd.item.pos.x, cmd.item.pos.z);
for (auto& lc : l->clients) {
if (!lc) {
@@ -2253,7 +2248,7 @@ static asio::awaitable<void> on_pick_up_item_generic(
}
try {
p->add_item(fi->data, *s->item_stack_limits(c->version()));
p->add_item(fi->data, *s->data->item_stack_limits(c->version()));
} catch (const std::out_of_range&) {
// Inventory is full; put the item back where it was
l->log.warning_f("Player {} requests to pick up {:08X}, but their inventory is full; dropping command",
@@ -2264,7 +2259,7 @@ static asio::awaitable<void> on_pick_up_item_generic(
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), fi->data);
auto name = s->data->describe_item(c->version(), fi->data);
l->log.info_f("Player {} picked up {:08X} ({})", client_id, item_id, name);
c->print_inventory();
}
@@ -2285,20 +2280,20 @@ static asio::awaitable<void> on_pick_up_item_generic(
uint32_t pi = fi->data.primary_identifier();
bool should_send_game_notif, should_send_global_notif;
if (is_v1_or_v2(c->version()) && (c->version() != Version::GC_NTE)) {
should_send_game_notif = s->notify_game_for_item_primary_identifiers_v1_v2.count(pi);
should_send_global_notif = s->notify_server_for_item_primary_identifiers_v1_v2.count(pi);
should_send_game_notif = s->data->notify_game_for_item_primary_identifiers_v1_v2.count(pi);
should_send_global_notif = s->data->notify_server_for_item_primary_identifiers_v1_v2.count(pi);
} else if (!is_v4(c->version())) {
should_send_game_notif = s->notify_game_for_item_primary_identifiers_v3.count(pi);
should_send_global_notif = s->notify_server_for_item_primary_identifiers_v3.count(pi);
should_send_game_notif = s->data->notify_game_for_item_primary_identifiers_v3.count(pi);
should_send_global_notif = s->data->notify_server_for_item_primary_identifiers_v3.count(pi);
} else {
should_send_game_notif = s->notify_game_for_item_primary_identifiers_v4.count(pi);
should_send_global_notif = s->notify_server_for_item_primary_identifiers_v4.count(pi);
should_send_game_notif = s->data->notify_game_for_item_primary_identifiers_v4.count(pi);
should_send_global_notif = s->data->notify_server_for_item_primary_identifiers_v4.count(pi);
}
if (should_send_game_notif || should_send_global_notif) {
std::string p_name = p->disp.visual.name.decode();
std::string desc_ingame = s->describe_item(c->version(), fi->data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
std::string desc_http = s->describe_item(c->version(), fi->data);
std::string desc_ingame = s->data->describe_item(c->version(), fi->data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
std::string desc_http = s->data->describe_item(c->version(), fi->data);
if (s->http_server) {
auto message = std::make_shared<phosg::JSON>(phosg::JSON::dict({
@@ -2391,7 +2386,7 @@ static void on_use_item(std::shared_ptr<Client> c, SubcommandMessage& msg) {
// Note: We manually downscope item here because player_use_item will likely move or delete the item, which will
// break the reference, so we don't want to accidentally use it again after that.
const auto& item = p->inventory.items[index].data;
name = s->describe_item(c->version(), item);
name = s->data->describe_item(c->version(), item);
}
player_use_item(c, index, l->rand_crypt);
@@ -2420,9 +2415,9 @@ static void on_feed_mag(std::shared_ptr<Client> c, SubcommandMessage& msg) {
{
// Note: We downscope these because player_feed_mag will likely delete the items, which will break these references
const auto& fed_item = p->inventory.items[fed_index].data;
fed_name = s->describe_item(c->version(), fed_item);
fed_name = s->data->describe_item(c->version(), fed_item);
const auto& mag_item = p->inventory.items[mag_index].data;
mag_name = s->describe_item(c->version(), mag_item);
mag_name = s->data->describe_item(c->version(), mag_item);
}
player_feed_mag(c, mag_index, fed_index);
@@ -2430,7 +2425,7 @@ static void on_feed_mag(std::shared_ptr<Client> c, SubcommandMessage& msg) {
// fed item. So on BB, we should remove the fed item here, but on other versions, we allow the following 6x29 command
// to do that.
if (c->version() == Version::BB_V4) {
p->remove_item(cmd.fed_item_id, 1, *s->item_stack_limits(c->version()));
p->remove_item(cmd.fed_item_id, 1, *s->data->item_stack_limits(c->version()));
}
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
@@ -2530,7 +2525,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(std::shared_ptr<Client> c, Subcom
}
for (auto& item : c->bb_shop_contents[cmd.shop_type]) {
item.id = 0xFFFFFFFF;
item.data2d = s->item_parameter_table(c->version())->price_for_item(item);
item.data2d = s->data->item_parameter_table(c->version())->price_for_item(item);
}
send_shop(c, cmd.shop_type);
@@ -2593,7 +2588,7 @@ static void on_ep3_private_word_select_bb_bank_action(std::shared_ptr<Client> c,
auto s = c->require_server_state();
if (is_ep3(c->version())) {
const auto& cmd = msg.check_size_t<G_PrivateWordSelect_Ep3_6xBD>();
s->word_select_table->validate(cmd.message, c->version());
s->data->word_select_table->validate(cmd.message, c->version());
std::string from_name = c->character_file()->disp.visual.name.decode(c->language());
static const std::string whisper_text = "(whisper)";
@@ -2665,7 +2660,7 @@ static void on_ep3_private_word_select_bb_bank_action(std::shared_ptr<Client> c,
}
} else { // Deposit item
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.item_amount, limits);
// If a stack was split, the bank item retains the same item ID as the inventory item. This is annoying but
// doesn't cause any problems because we always generate a new item ID when withdrawing from the bank, so
@@ -2677,7 +2672,7 @@ static void on_ep3_private_word_select_bb_bank_action(std::shared_ptr<Client> c,
send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
std::string name = s->describe_item(Version::BB_V4, item);
std::string name = s->data->describe_item(Version::BB_V4, item);
l->log.info_f("Player {} deposited item {:08X} (x{}) ({}) in the bank",
c->lobby_client_id, cmd.item_id, cmd.item_amount, name);
c->print_inventory();
@@ -2700,14 +2695,14 @@ static void on_ep3_private_word_select_bb_bank_action(std::shared_ptr<Client> c,
}
} else { // Take item
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
auto item = bank->remove_item(cmd.item_id, cmd.item_amount, limits);
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
std::string name = s->describe_item(Version::BB_V4, item);
std::string name = s->data->describe_item(Version::BB_V4, item);
l->log.info_f("Player {} withdrew item {:08X} (x{}) ({}) from the bank",
c->lobby_client_id, item.id, cmd.item_amount, name);
c->print_inventory();
@@ -2991,7 +2986,7 @@ static void on_entity_drop_item_request(std::shared_ptr<Client> c, SubcommandMes
if (res.item.empty()) {
l->log.info_f("No item was created");
} else {
std::string name = s->describe_item(c->version(), res.item);
std::string name = s->data->describe_item(c->version(), res.item);
l->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
if (drop_mode == ServerDropMode::SERVER_DUPLICATE) {
for (const auto& lc : l->clients) {
@@ -3029,7 +3024,7 @@ static void on_entity_drop_item_request(std::shared_ptr<Client> c, SubcommandMes
if (res.item.empty()) {
l->log.info_f("No item was created for {}", lc->channel->name);
} else {
std::string name = s->describe_item(lc->version(), res.item);
std::string name = s->data->describe_item(lc->version(), res.item);
l->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
res.item.id = l->generate_item_id(0xFF);
l->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for {}",
@@ -3846,7 +3841,7 @@ static void on_charge_attack_bb(std::shared_ptr<Client> c, SubcommandMessage& ms
static void send_max_level_notification_if_needed(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
if (!s->notify_server_for_max_level_achieved) {
if (!s->data->notify_server_for_max_level_achieved) {
return;
}
@@ -3886,7 +3881,7 @@ static void on_level_up(std::shared_ptr<Client> c, SubcommandMessage& msg) {
if (is_pre_v1(c->version())) {
msg.check_size_t<G_ChangePlayerLevel_DCNTE_6x30>();
auto s = c->require_server_state();
auto level_table = s->level_table(c->version());
auto level_table = s->data->level_table(c->version());
const auto& incrs = level_table->stats_delta_for_level(p->disp.visual.sh.char_class, p->disp.stats.level + 1);
p->disp.stats.char_stats.atp += incrs.atp;
p->disp.stats.char_stats.mst += incrs.mst;
@@ -3922,7 +3917,7 @@ static void add_player_exp(std::shared_ptr<Client> c, uint32_t exp, uint16_t fro
bool leveled_up = false;
do {
const auto& level = s->level_table(c->version())->stats_delta_for_level(p->disp.visual.sh.char_class, p->disp.stats.level + 1);
const auto& level = s->data->level_table(c->version())->stats_delta_for_level(p->disp.visual.sh.char_class, p->disp.stats.level + 1);
if (p->disp.stats.exp >= level.exp) {
leveled_up = true;
level.apply(p->disp.stats.char_stats);
@@ -4027,7 +4022,7 @@ static void on_steal_exp_bb(std::shared_ptr<Client> c, SubcommandMessage& msg) {
const auto& inventory = p->inventory;
const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)];
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
uint8_t special_id = 0;
if (((weapon.data.data1[1] < 0x0A) && (weapon.data.data1[2] < 0x05)) ||
@@ -4046,7 +4041,7 @@ static void on_steal_exp_bb(std::shared_ptr<Client> c, SubcommandMessage& msg) {
Episode episode = episode_for_area(area);
auto type = ene_st->type(c->version(), area, l->difficulty, l->event);
uint32_t enemy_exp = base_exp_for_enemy_type(
s->battle_params, l->quest, type, episode, l->difficulty, ene_st->super_ene->floor, l->mode == GameMode::SOLO);
s->data->battle_params, l->quest, type, episode, l->difficulty, ene_st->super_ene->floor, l->mode == GameMode::SOLO);
// Note: The original code checks if special.type is 9, 10, or 11, and skips applying the android bonus if so. We
// don't do anything for those special types, so we don't check for that here.
@@ -4098,7 +4093,7 @@ static void on_enemy_exp_request_bb(std::shared_ptr<Client> c, SubcommandMessage
auto& inventory = c->character_file()->inventory;
for (size_t z = 0; z < inventory.num_items; z++) {
auto& item = inventory.items[z];
if ((item.flags & 0x08) && s->item_parameter_table(c->version())->is_unsealable_item(item.data)) {
if ((item.flags & 0x08) && s->data->item_parameter_table(c->version())->is_unsealable_item(item.data)) {
size_t new_kill_count = item.data.get_kill_count() + 1;
item.data.set_kill_count(new_kill_count);
c->log.info_f("Item {:08X} kill count updated to {}", item.data.id, new_kill_count);
@@ -4110,7 +4105,7 @@ static void on_enemy_exp_request_bb(std::shared_ptr<Client> c, SubcommandMessage
Episode episode = episode_for_area(area);
auto type = ene_st->type(c->version(), area, l->difficulty, l->event);
double base_exp = base_exp_for_enemy_type(
s->battle_params, l->quest, type, episode, l->difficulty, ene_st->super_ene->floor, l->mode == GameMode::SOLO);
s->data->battle_params, l->quest, type, episode, l->difficulty, ene_st->super_ene->floor, l->mode == GameMode::SOLO);
// If this player killed the enemy, they get full EXP; if they tagged the enemy, they get 80% EXP; if auto EXP share
// is enabled and they are close enough to the monster, they get a smaller share; if none of these situations apply,
@@ -4204,7 +4199,7 @@ static void on_adjust_player_meseta_bb(std::shared_ptr<Client> c, SubcommandMess
item.data1[0] = 0x04;
item.data2d = cmd.amount;
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, *s->item_stack_limits(c->version()));
p->add_item(item, *s->data->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
}
@@ -4240,7 +4235,7 @@ static void on_quest_create_item_bb(std::shared_ptr<Client> c, SubcommandMessage
const auto& cmd = msg.check_size_t<G_QuestCreateItem_BB_6xCA>();
auto s = c->require_server_state();
auto l = c->require_lobby();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
ItemData item;
item = cmd.item_data;
@@ -4261,7 +4256,7 @@ static void on_quest_create_item_bb(std::shared_ptr<Client> c, SubcommandMessage
c->character_file()->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} created inventory item {:08X} ({}) via quest command",
c->lobby_client_id, item.id, name);
c->print_inventory();
@@ -4269,7 +4264,7 @@ static void on_quest_create_item_bb(std::shared_ptr<Client> c, SubcommandMessage
} catch (const std::out_of_range&) {
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} attempted to create inventory item {:08X} ({}) via quest command, but it cannot be placed in their inventory",
c->lobby_client_id, item.id, name);
}
@@ -4292,11 +4287,11 @@ static void on_transfer_item_via_mail_message_bb(std::shared_ptr<Client> c, Subc
auto s = c->require_server_state();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} sent inventory item {}:{:08X} ({}) x{} to player {:08X}",
c->lobby_client_id, cmd.header.client_id, cmd.item_id, name, cmd.amount, cmd.target_guild_card_number);
c->print_inventory();
@@ -4353,15 +4348,15 @@ static void on_exchange_item_for_team_points_bb(std::shared_ptr<Client> c, Subco
auto s = c->require_server_state();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
size_t amount = item.stack_size(limits);
size_t points = s->item_parameter_table(Version::BB_V4)->get_item_team_points(item);
size_t points = s->data->item_parameter_table(Version::BB_V4)->get_item_team_points(item);
s->team_index->add_member_points(c->login->account->account_id, points * amount);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} exchanged inventory item {}:{:08X} ({}) x{} for {} * {} = {} team points",
c->lobby_client_id, cmd.header.client_id, cmd.item_id, name, amount, points, amount, points * amount);
c->print_inventory();
@@ -4389,10 +4384,10 @@ static void on_destroy_inventory_item(std::shared_ptr<Client> c, SubcommandMessa
auto s = c->require_server_state();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->data->item_stack_limits(c->version()));
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} destroyed inventory item {}:{:08X} ({})",
c->lobby_client_id, cmd.header.client_id, cmd.item_id, name);
c->print_inventory();
@@ -4441,7 +4436,7 @@ static void on_destroy_floor_item(std::shared_ptr<Client> c, SubcommandMessage&
c->lobby_client_id, cmd.item_id);
} else {
auto name = s->describe_item(c->version(), fi->data);
auto name = s->data->describe_item(c->version(), fi->data);
l->log.info_f("Player {} destroyed floor item {:08X} ({})", c->lobby_client_id, cmd.item_id, name);
// Only forward to players for whom the item was visible
@@ -4525,7 +4520,7 @@ static void on_accept_identify_item_bb(std::shared_ptr<Client> c, SubcommandMess
throw std::runtime_error("accepted item ID does not match previous identify request");
}
auto s = c->require_server_state();
c->character_file()->add_item(c->bb_identify_result, *s->item_stack_limits(c->version()));
c->character_file()->add_item(c->bb_identify_result, *s->data->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, c->bb_identify_result);
c->bb_identify_result.clear();
}
@@ -4544,12 +4539,12 @@ static void on_sell_item_at_shop_bb(std::shared_ptr<Client> c, SubcommandMessage
auto s = c->require_server_state();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
size_t price = (s->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount;
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->data->item_stack_limits(c->version()));
size_t price = (s->data->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount;
p->add_meseta(price);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} sold inventory item {:08X} ({}) for {} Meseta",
c->lobby_client_id, cmd.item_id, name, price);
c->print_inventory();
@@ -4569,7 +4564,7 @@ static void on_buy_shop_item_bb(std::shared_ptr<Client> c, SubcommandMessage& ms
const auto& cmd = msg.check_size_t<G_BuyShopItem_BB_6xB7>();
auto s = c->require_server_state();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
ItemData item;
item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index);
@@ -4591,7 +4586,7 @@ static void on_buy_shop_item_bb(std::shared_ptr<Client> c, SubcommandMessage& ms
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item);
auto name = s->data->describe_item(c->version(), item);
l->log.info_f("Player {} purchased item {:08X} ({}) for {} meseta", c->lobby_client_id, item.id, name, price);
c->print_inventory();
}
@@ -4645,7 +4640,7 @@ static void on_battle_restart_bb(std::shared_ptr<Client> c, SubcommandMessage& m
if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index);
}
lc->create_battle_overlay(new_rules, s->level_table(c->version()));
lc->create_battle_overlay(new_rules, s->data->level_table(c->version()));
}
}
l->map_state->reset();
@@ -4683,7 +4678,7 @@ static void on_battle_level_up_bb(std::shared_ptr<Client> c, SubcommandMessage&
auto lp = lc->character_file();
uint32_t target_level = std::min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199);
uint32_t before_exp = lp->disp.stats.exp;
s->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.sh.char_class);
s->data->level_table(lc->version())->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.sh.char_class);
if ((lp->disp.stats.exp > before_exp) && (lc->version() == Version::BB_V4)) {
send_give_experience(lc, lp->disp.stats.exp - before_exp, 0xFFFF);
send_level_up(lc);
@@ -4718,7 +4713,7 @@ static void on_battle_tech_level_up(std::shared_ptr<Client> c, SubcommandMessage
if (lc) {
auto s = c->require_server_state();
auto lp = lc->character_file();
auto pmt = s->item_parameter_table(lc->version());
auto pmt = s->data->item_parameter_table(lc->version());
for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) {
size_t level = lp->get_technique_level(tech_num);
if (level != 0xFF) {
@@ -4789,7 +4784,7 @@ static void on_challenge_mode_retry_or_quit(std::shared_ptr<Client> c, Subcomman
if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index);
}
lc->create_challenge_overlay(lc->version(), l->quest->meta.challenge_template_index, s->level_table(c->version()));
lc->create_challenge_overlay(lc->version(), l->quest->meta.challenge_template_index, s->data->level_table(c->version()));
lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
}
@@ -4958,7 +4953,7 @@ static void on_quest_exchange_item_bb(std::shared_ptr<Client> c, SubcommandMessa
try {
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
ItemData new_item = cmd.replace_item;
assert_quest_item_create_allowed(l, new_item);
@@ -4993,10 +4988,10 @@ static void on_wrap_item_bb(std::shared_ptr<Client> c, SubcommandMessage& msg) {
auto s = c->require_server_state();
auto p = c->character_file();
auto item = p->remove_item(cmd.item.id, 1, *s->item_stack_limits(c->version()));
auto item = p->remove_item(cmd.item.id, 1, *s->data->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1);
item.wrap(*s->item_stack_limits(c->version()), cmd.present_color);
p->add_item(item, *s->item_stack_limits(c->version()));
item.wrap(*s->data->item_stack_limits(c->version()), cmd.present_color);
p->add_item(item, *s->data->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
}
@@ -5014,7 +5009,7 @@ static void on_photon_drop_exchange_for_item_bb(std::shared_ptr<Client> c, Subco
try {
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
ItemData new_item = cmd.new_item;
assert_quest_item_create_allowed(l, new_item);
@@ -5047,7 +5042,7 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(std::shared_ptr<Client
const auto& cmd = msg.check_size_t<G_AddSRankWeaponSpecial_BB_6xD8>();
auto s = c->require_server_state();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
try {
auto p = c->character_file();
@@ -5142,7 +5137,7 @@ static void on_secret_lottery_ticket_exchange_bb(std::shared_ptr<Client> c, Subc
item.data1[z] = r.min;
}
auto s = c->require_server_state();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
item.enforce_stack_size_limits(limits);
uint32_t slt_item_id = p->inventory.items[currency_index].data.id;
@@ -5175,7 +5170,7 @@ static void on_photon_crystal_exchange_bb(std::shared_ptr<Client> c, SubcommandM
auto s = c->require_server_state();
auto p = c->character_file();
size_t index = p->inventory.find_item_by_primary_identifier(0x03100200);
auto item = p->remove_item(p->inventory.items[index].data.id, 1, *s->item_stack_limits(c->version()));
auto item = p->remove_item(p->inventory.items[index].data.id, 1, *s->data->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1);
l->drop_mode = ServerDropMode::DISABLED;
l->allowed_drop_modes = (1 << static_cast<uint8_t>(l->drop_mode)); // DISABLED only
@@ -5199,7 +5194,7 @@ static void on_quest_F95E_result_bb(std::shared_ptr<Client> c, SubcommandMessage
size_t count = (cmd.type > 0x03) ? 1 : (static_cast<size_t>(l->difficulty) + 1);
c->log.info_f("Creating {} F95E result items", count);
for (size_t z = 0; z < count; z++) {
const auto& results = s->quest_F95E_results.at(cmd.type).at(static_cast<size_t>(l->difficulty));
const auto& results = s->data->quest_F95E_results.at(cmd.type).at(static_cast<size_t>(l->difficulty));
if (results.empty()) {
throw std::runtime_error("invalid result type");
}
@@ -5214,7 +5209,7 @@ static void on_quest_F95E_result_bb(std::shared_ptr<Client> c, SubcommandMessage
} else if (item.data1[0] == 0x00) {
item.data1[4] |= 0x80; // Unidentified
} else {
item.enforce_stack_size_limits(*s->item_stack_limits(c->version()));
item.enforce_stack_size_limits(*s->data->item_stack_limits(c->version()));
}
item.id = l->generate_item_id(0xFF);
@@ -5244,12 +5239,12 @@ static void on_quest_F95F_result_bb(std::shared_ptr<Client> c, SubcommandMessage
auto s = c->require_server_state();
auto p = c->character_file();
const auto& result = s->quest_F95F_results.at(cmd.result_index);
const auto& result = s->data->quest_F95F_results.at(cmd.result_index);
if (result.second.empty()) {
throw std::runtime_error("invalid result index");
}
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
bool failed = false;
ItemData ticket_item;
@@ -5309,7 +5304,7 @@ static void on_quest_F960_result_bb(std::shared_ptr<Client> c, SubcommandMessage
ItemData item;
for (size_t num_failures = 0; num_failures <= cmd.result_tier; num_failures++) {
size_t tier = cmd.result_tier - num_failures;
const auto& results = s->quest_F960_success_results.at(tier);
const auto& results = s->data->quest_F960_success_results.at(tier);
uint64_t probability = results.base_probability + num_failures * results.probability_upgrade;
if (l->rand_crypt->next() <= probability) {
c->log.info_f("Tier {} yielded a prize", tier);
@@ -5322,7 +5317,7 @@ static void on_quest_F960_result_bb(std::shared_ptr<Client> c, SubcommandMessage
}
if (item.empty()) {
c->log.info_f("Choosing result from failure tier");
const auto& result_items = s->quest_F960_failure_results.results.at(weekday);
const auto& result_items = s->data->quest_F960_failure_results.results.at(weekday);
item = result_items[l->rand_crypt->next() % result_items.size()];
}
if (item.empty()) {
@@ -5333,7 +5328,7 @@ static void on_quest_F960_result_bb(std::shared_ptr<Client> c, SubcommandMessage
item.id = l->generate_item_id(c->lobby_client_id);
// If it's a weapon, make it unidentified
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
if ((item.data1[0] == 0x00) && (item_parameter_table->is_item_rare(item) || (item.data1[4] != 0))) {
item.data1[4] |= 0x80;
}
@@ -5347,7 +5342,7 @@ static void on_quest_F960_result_bb(std::shared_ptr<Client> c, SubcommandMessage
// Add the item to the player's inventory if possible; if not, drop it on the floor where the player is standing
bool added_to_inventory;
try {
p->add_item(item, *s->item_stack_limits(c->version()));
p->add_item(item, *s->data->item_stack_limits(c->version()));
added_to_inventory = true;
} catch (const std::out_of_range&) {
// If the game's drop mode is private or duplicate, make the item visible only to this player; in other modes, make
@@ -5360,7 +5355,7 @@ static void on_quest_F960_result_bb(std::shared_ptr<Client> c, SubcommandMessage
}
if (c->log.should_log(phosg::LogLevel::L_INFO)) {
std::string name = s->describe_item(c->version(), item);
std::string name = s->data->describe_item(c->version(), item);
c->log.info_f("Awarded item {} {}", name, added_to_inventory ? "in inventory" : "on ground (inventory is full)");
}
if (added_to_inventory) {
@@ -5387,7 +5382,7 @@ static void on_momoka_item_exchange_bb(std::shared_ptr<Client> c, SubcommandMess
auto s = c->require_server_state();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
const auto& limits = *s->data->item_stack_limits(c->version());
ItemData new_item = cmd.replace_item;
assert_quest_item_create_allowed(l, new_item);
@@ -5449,7 +5444,7 @@ static void on_upgrade_weapon_attribute_bb(std::shared_ptr<Client> c, Subcommand
uint32_t payment_primary_identifier = cmd.payment_type ? 0x03100100 : 0x03100000;
size_t payment_index = p->inventory.find_item_by_primary_identifier(payment_primary_identifier);
auto& payment_item = p->inventory.items[payment_index].data;
if (payment_item.stack_size(*s->item_stack_limits(c->version())) < cmd.payment_count) {
if (payment_item.stack_size(*s->data->item_stack_limits(c->version())) < cmd.payment_count) {
throw std::runtime_error("not enough payment items present");
}
@@ -5480,7 +5475,7 @@ static void on_upgrade_weapon_attribute_bb(std::shared_ptr<Client> c, Subcommand
}
auto removed_payment_item = p->remove_item(
payment_item.id, cmd.payment_count, *s->item_stack_limits(c->version()));
payment_item.id, cmd.payment_count, *s->data->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, removed_payment_item.id, cmd.payment_count);
item.data1[attribute_index] = cmd.attribute;
+58 -49
View File
@@ -319,19 +319,17 @@ void ReplaySession::apply_default_mask(std::shared_ptr<Event> ev) {
break;
}
default:
throw std::logic_error("invalid game version");
throw std::logic_error("Invalid game version");
}
}
ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
: state(state),
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
commands_sent(0),
bytes_sent(0),
commands_received(0),
bytes_received(0),
idle_timeout_timer(*this->state->io_context),
run_failed(false) {
idle_timeout_timer(*this->state->io_context) {
std::shared_ptr<Event> parsing_command = nullptr;
size_t line_num = 0;
@@ -364,16 +362,16 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
}
if (line == "### use psov2 crypt") {
this->state->use_psov2_rand_crypt = true;
this->use_psov2_rand_crypt = true;
}
if (line == "### use legacy item random behavior") {
this->state->use_legacy_item_random_behavior = true;
this->use_legacy_item_random_behavior = true;
}
if (line.starts_with("### cc ")) {
// ### cc $<chat command>
if (this->clients.size() != 1) {
throw std::runtime_error(std::format(
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
"(ev-line {}) Bare `cc` shortcut cannot be used with multiple clients connected; use `on C-X cc` instead",
line_num));
}
std::shared_ptr<Event> event;
@@ -383,7 +381,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
event->data = encode_chat_message(c->version, line.substr(7));
num_events++;
} catch (const std::exception& e) {
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
throw std::runtime_error(std::format("(ev-line {}) Failed to generate chat message ({})", line_num, e.what()));
}
continue;
@@ -400,7 +398,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
num_events++;
} catch (const std::exception& e) {
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
throw std::runtime_error(std::format("(ev-line {}) Failed to generate chat message ({})", line_num, e.what()));
}
continue;
@@ -412,21 +410,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
auto tokens = phosg::split(line, ' ');
if (!tokens[8].starts_with("C-")) {
throw std::runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client connection message missing client ID token", line_num));
}
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
auto listen_tokens = phosg::split(tokens[10], '-');
if (listen_tokens.size() < 4) {
throw std::runtime_error(std::format(
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
"(ev-line {}) Client connection message listening socket token format is incorrect", line_num));
}
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
if (!this->clients.emplace(c->id, c).second) {
throw std::runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
throw std::runtime_error(std::format("(ev-line {}) Duplicate client ID in input log", line_num));
}
this->create_event(Event::Type::CONNECT, c, line_num);
num_events++;
@@ -438,21 +436,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' ');
if (tokens.size() < 11) {
throw std::runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client disconnection message has incorrect token count", line_num));
}
if (!tokens[10].starts_with("C-")) {
throw std::runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client disconnection message missing client ID token", line_num));
}
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
try {
auto& c = this->clients.at(client_id);
if (c->disconnect_event.get()) {
throw std::runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client has multiple disconnect events", line_num));
}
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
num_events++;
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
throw std::runtime_error(std::format("(ev-line {}) Unknown disconnecting client ID in input log", line_num));
}
continue;
}
@@ -466,7 +464,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' ');
if (tokens.size() < 10) {
throw std::runtime_error(std::format("(ev-line {}) command header line too short", line_num));
throw std::runtime_error(std::format("(ev-line {}) Command header line too short", line_num));
}
bool from_client = (tokens[6] == "Received");
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
@@ -475,7 +473,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
num_events++;
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
throw std::runtime_error(std::format("(ev-line {}) Input log contains command for missing client", line_num));
}
continue;
}
@@ -494,8 +492,12 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
}
asio::awaitable<void> ReplaySession::run() {
bool prev_use_psov2_rand_crypt = this->state->use_psov2_rand_crypt;
bool prev_use_legacy_item_random_behavior = this->state->use_legacy_item_random_behavior;
this->state->use_psov2_rand_crypt = this->use_psov2_rand_crypt;
this->state->use_legacy_item_random_behavior = this->use_legacy_item_random_behavior;
try {
replay_log.info_f("Starting replay");
while (this->first_event) {
if (!this->first_event->complete) {
auto& c = this->clients.at(this->first_event->client_id);
@@ -505,15 +507,15 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::CONNECT: {
if (c->channel->connected()) {
throw std::runtime_error(std::format(
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
"(ev-line {}) Connect event on already-connected client", this->first_event->line_num));
}
std::shared_ptr<const PortConfiguration> port_config;
const DataIndex::PortConfiguration* port_config;
try {
port_config = this->state->number_to_port_config.at(c->port);
port_config = &this->state->data->number_to_port_config.at(c->port);
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format(
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
"(ev-line {}) Client connected to port missing from configuration", this->first_event->line_num));
}
auto server_channel = std::make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
@@ -523,7 +525,7 @@ asio::awaitable<void> ReplaySession::run() {
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
} else {
throw std::runtime_error(std::format(
"(ev-line {}) no server available for connection", this->first_event->line_num));
"(ev-line {}) No server available for connection", this->first_event->line_num));
}
break;
}
@@ -535,7 +537,7 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::SEND:
if (!c->channel->connected()) {
throw std::runtime_error(std::format(
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
"(ev-line {}) Send event attempted on unconnected client", this->first_event->line_num));
}
c->channel->send(this->first_event->data);
this->commands_sent++;
@@ -544,8 +546,8 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::RECEIVE: {
if (!c->channel->connected()) {
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client",
this->first_event->line_num));
throw std::runtime_error(std::format(
"(ev-line {}) Receive event on non-connected client", this->first_event->line_num));
}
if (c->receive_events.front() != this->first_event) {
throw std::logic_error("Client receive events are out of order");
@@ -561,25 +563,29 @@ asio::awaitable<void> ReplaySession::run() {
this->bytes_received += full_command.size();
if (c->receive_events.empty()) {
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error("received unexpected command for client");
std::string data_str = phosg::format_data(full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format("Received unexpected command for client:\n{}", data_str));
}
auto& ev = c->receive_events.front();
if ((full_command.size() != ev->data.size()) && !ev->allow_size_disparity) {
replay_log.error_f("Expected command:");
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:");
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
std::string expected_data = phosg::format_data(
ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
std::string received_data = phosg::format_data(
full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format(
"(ev-line {}) Received command sizes do not match:\nExpected command:\n{}\nReceived command:\n{}",
ev->line_num, expected_data, received_data));
}
for (size_t x = 0; x < std::min<size_t>(full_command.size(), ev->data.size()); x++) {
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
replay_log.error_f("Expected command:");
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:");
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
std::string expected_data = phosg::format_data(
ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
std::string received_data = phosg::format_data(
full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format(
"(ev-line {}) Received command does not match expected\nExpected command:\n{}\nReceived command:\n{}",
ev->line_num, expected_data, received_data));
}
}
@@ -624,18 +630,18 @@ asio::awaitable<void> ReplaySession::run() {
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
// first one here.
c->channel->crypt_in = std::make_shared<PSOBBEncryption>(
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
*this->state->data->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
c->channel->crypt_out = std::make_shared<PSOBBEncryption>(
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
*this->state->data->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
}
break;
default:
throw std::logic_error("unsupported encryption version");
throw std::logic_error("Unsupported encryption version");
}
break;
}
default:
throw std::logic_error("unhandled event type");
throw std::logic_error("Unhandled event type");
}
this->first_event->complete = true;
}
@@ -646,13 +652,12 @@ asio::awaitable<void> ReplaySession::run() {
}
}
} catch (const std::exception& e) {
replay_log.error_f("Replay failed: {}", e.what());
this->failure = std::format("Replay failed: {}", e.what());
if (this->first_event) {
replay_log.error_f("Next pending event: {}", this->first_event->str());
this->failure += std::format("\nNext pending event: {}", this->first_event->str());
} else {
replay_log.error_f("No events are pending at failure time");
this->failure += std::format("\nNo events are pending at failure time");
}
this->run_failed = true;
}
for (auto& [_, c] : this->clients) {
@@ -660,14 +665,18 @@ asio::awaitable<void> ReplaySession::run() {
c->channel->disconnect();
}
}
this->state->use_psov2_rand_crypt = this->prev_psov2_crypt_enabled;
if (!this->run_failed) {
this->state->use_psov2_rand_crypt = prev_use_psov2_rand_crypt;
this->state->use_legacy_item_random_behavior = prev_use_legacy_item_random_behavior;
if (this->failure.empty()) {
// Wait a bit longer to ensure that any command sent at the end of the replay session don't crash the server
co_await async_sleep(std::chrono::seconds(2));
replay_log.info_f("Replay complete: {} commands sent ({} bytes), {} commands received ({} bytes)",
this->commands_sent, this->bytes_sent, this->commands_received, this->bytes_received);
}
this->idle_timeout_timer.cancel();
}
void ReplaySession::reschedule_idle_timeout() {
+6 -4
View File
@@ -21,8 +21,9 @@ public:
~ReplaySession() = default;
asio::awaitable<void> run();
inline bool failed() const {
return this->run_failed;
inline std::string failure_str() const {
return this->failure;
}
private:
@@ -62,7 +63,8 @@ private:
};
std::shared_ptr<ServerState> state;
bool prev_psov2_crypt_enabled;
bool use_psov2_rand_crypt = false;
bool use_legacy_item_random_behavior = false;
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
@@ -75,7 +77,7 @@ private:
size_t bytes_received;
asio::steady_timer idle_timeout_timer;
bool run_failed;
std::string failure;
std::shared_ptr<ReplaySession::Event> create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num);
+62 -62
View File
@@ -232,7 +232,7 @@ void send_server_init_bb(std::shared_ptr<Client> c, uint8_t flags) {
send_command_t(c, use_secondary_message ? 0x9B : 0x03, 0x00, cmd);
c->bb_detector_crypt = std::make_shared<PSOBBMultiKeyDetectorEncryption>(
c->require_server_state()->bb_private_keys,
c->require_server_state()->data->bb_private_keys,
bb_crypt_initial_client_commands,
cmd.basic_cmd.client_key.data(),
sizeof(cmd.basic_cmd.client_key));
@@ -336,7 +336,7 @@ asio::awaitable<void> prepare_client_for_patches(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
if (!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) {
auto fn = s->client_functions->get("CacheClearFix-Phase1", ClientFunctionIndex::Function::Architecture::POWERPC);
auto fn = s->data->client_functions->get("CacheClearFix-Phase1", ClientFunctionIndex::Function::Architecture::POWERPC);
std::unordered_map<std::string, uint32_t> label_writes;
auto call1_res = co_await send_function_call(c, fn, label_writes, nullptr, 0, 0x80000000, 8, 0x7F2734EC);
try {
@@ -345,7 +345,7 @@ asio::awaitable<void> prepare_client_for_patches(std::shared_ptr<Client> c) {
} catch (const std::out_of_range&) {
c->log.info_f("Could not detect specific version from header checksum {:08X}", call1_res.checksum);
}
co_await send_function_call(c, s->client_functions->get("CacheClearFix-Phase2", ClientFunctionIndex::Function::Architecture::POWERPC));
co_await send_function_call(c, s->data->client_functions->get("CacheClearFix-Phase2", ClientFunctionIndex::Function::Architecture::POWERPC));
c->log.info_f("Client cache behavior patched");
c->set_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
}
@@ -360,7 +360,7 @@ asio::awaitable<void> prepare_client_for_patches(std::shared_ptr<Client> c) {
}
if ((arch != ClientFunctionIndex::Function::Architecture::UNKNOWN) &&
specific_version_is_indeterminate(c->specific_version)) {
auto vers_detect_res = co_await send_function_call(c, s->client_functions->get("VersionDetect", arch));
auto vers_detect_res = co_await send_function_call(c, s->data->client_functions->get("VersionDetect", arch));
c->specific_version = vers_detect_res.return_value;
c->log.info_f("Version detected as {:08X}", c->specific_version);
}
@@ -517,7 +517,7 @@ asio::awaitable<bool> send_protected_command(std::shared_ptr<Client> c, const vo
case Version::GC_EP3:
case Version::BB_V4: {
auto s = c->require_server_state();
if (!s->enable_v3_v4_protected_subcommands ||
if (!s->data->enable_v3_v4_protected_subcommands ||
!c->check_flag(Client::Flag::HAS_SEND_FUNCTION_CALL) ||
!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE)) {
co_return false;
@@ -526,7 +526,7 @@ asio::awaitable<bool> send_protected_command(std::shared_ptr<Client> c, const vo
co_await prepare_client_for_patches(c);
try {
auto fn = s->client_functions->get("CallProtectedHandler", c->specific_version);
auto fn = s->data->client_functions->get("CallProtectedHandler", c->specific_version);
std::unordered_map<std::string, uint32_t> label_writes{{"size", size}};
co_await send_function_call(c, fn, label_writes, data, size);
auto l = echo_to_lobby ? c->lobby.lock() : nullptr;
@@ -550,7 +550,7 @@ asio::awaitable<void> send_dol_file(std::shared_ptr<Client> c, std::shared_ptr<D
// Determine the necessary start address for the data
std::unordered_map<std::string, uint32_t> label_writes{{"address", 0x80000034}}; // ArenaHigh from GC globals
auto addr_ret = co_await send_function_call(
c, s->client_functions->get("ReadMemoryWord", c->specific_version), label_writes);
c, s->data->client_functions->get("ReadMemoryWord", c->specific_version), label_writes);
uint32_t dol_base_addr = (addr_ret.return_value - dol->data.size()) & (~3);
// Write the file in multiple chunks
@@ -562,7 +562,7 @@ asio::awaitable<void> send_dol_file(std::shared_ptr<Client> c, std::shared_ptr<D
std::string data_to_send = dol->data.substr(offset, bytes_to_send);
auto s = c->require_server_state();
auto fn = s->client_functions->get("WriteMemory", c->specific_version);
auto fn = s->data->client_functions->get("WriteMemory", c->specific_version);
label_writes = {{"dest_addr", (dol_base_addr + offset)}, {"size", bytes_to_send}};
co_await send_function_call(c, fn, label_writes, data_to_send.data(), data_to_send.size());
@@ -573,7 +573,7 @@ asio::awaitable<void> send_dol_file(std::shared_ptr<Client> c, std::shared_ptr<D
}
// Send the final function, which moves the DOL's sections into place and calls the entrypoint
auto fn = s->client_functions->get("RunDOL", c->specific_version);
auto fn = s->data->client_functions->get("RunDOL", c->specific_version);
label_writes = {{"dol_base_ptr", dol_base_addr}};
co_await send_function_call(c, fn, label_writes);
// The client will stop running PSO after this, so disconnect them
@@ -700,7 +700,7 @@ void send_stream_file_index_bb(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
std::vector<S_StreamFileIndexEntry_BB_01EB> entries;
for (const auto& sf_entry : s->bb_stream_file->entries) {
for (const auto& sf_entry : s->data->bb_stream_file->entries) {
auto& e = entries.emplace_back();
e.size = sf_entry.size;
e.checksum = sf_entry.checksum;
@@ -716,11 +716,11 @@ void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index)
S_StreamFileChunk_BB_02EB chunk_cmd;
chunk_cmd.chunk_index = chunk_index;
size_t offset = sizeof(chunk_cmd.data) * chunk_index;
if (offset > s->bb_stream_file->data.size()) {
if (offset > s->data->bb_stream_file->data.size()) {
throw std::runtime_error("client requested chunk beyond end of stream file");
}
size_t bytes = std::min<size_t>(s->bb_stream_file->data.size() - offset, sizeof(chunk_cmd.data));
chunk_cmd.data.assign_range(reinterpret_cast<const uint8_t*>(s->bb_stream_file->data.data() + offset), bytes, 0);
size_t bytes = std::min<size_t>(s->data->bb_stream_file->data.size() - offset, sizeof(chunk_cmd.data));
chunk_cmd.data.assign_range(reinterpret_cast<const uint8_t*>(s->data->bb_stream_file->data.data() + offset), bytes, 0);
size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes;
cmd_size = (cmd_size + 3) & ~3;
@@ -1183,17 +1183,17 @@ void send_card_search_result_t(std::shared_ptr<Client> c, std::shared_ptr<Client
cmd.reconnect_command_header.size = sizeof(cmd.reconnect_command_header) + sizeof(cmd.reconnect_command);
cmd.reconnect_command_header.command = 0x19;
cmd.reconnect_command_header.flag = 0x00;
cmd.reconnect_command.address = s->connect_address_for_client(c);
cmd.reconnect_command.port = s->game_server_port_for_version(c->version());
cmd.reconnect_command.address = s->data->connect_address_for_client(c);
cmd.reconnect_command.port = s->data->game_server_port_for_version(c->version());
cmd.reconnect_command.unused = 0;
std::string location_string;
if (result_lobby->is_game()) {
location_string = std::format("{},,BLOCK01,{}", result_lobby->name, s->name);
location_string = std::format("{},,BLOCK01,{}", result_lobby->name, s->data->name);
} else if (result_lobby->is_ep3()) {
location_string = std::format("BLOCK01-C{:02},,BLOCK01,{}", result_lobby->lobby_id - 15, s->name);
location_string = std::format("BLOCK01-C{:02},,BLOCK01,{}", result_lobby->lobby_id - 15, s->data->name);
} else {
location_string = std::format("BLOCK01-{:02},,BLOCK01,{}", result_lobby->lobby_id, s->name);
location_string = std::format("BLOCK01-{:02},,BLOCK01,{}", result_lobby->lobby_id, s->data->name);
}
cmd.location_string.encode(location_string, c->language());
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
@@ -1467,7 +1467,7 @@ void send_game_menu_t(std::shared_ptr<Client> c, bool is_spectator_team_list, bo
e.item_id = 0x00000000;
e.difficulty_tag = 0x00;
e.num_players = 0x00;
e.name.encode(s->name, c->language());
e.name.encode(s->data->name, c->language());
e.episode = 0x00;
e.flags = 0x04;
}
@@ -1623,7 +1623,7 @@ void send_quest_categories_menu_t(std::shared_ptr<Client> c, QuestMenuType menu_
std::vector<EntryT> entries;
auto s = c->require_server_state();
for (const auto& cat : s->quest_index->categories(menu_type, episode, version_flags, include_condition)) {
for (const auto& cat : s->data->quest_index->categories(menu_type, episode, version_flags, include_condition)) {
auto& e = entries.emplace_back();
e.menu_id = cat->use_ep2_icon() ? MenuID::QUEST_CATEGORIES_EP2 : MenuID::QUEST_CATEGORIES_EP1_EP3_EP4;
e.item_id = cat->category_id;
@@ -1646,7 +1646,7 @@ void send_ep3_download_quest_categories_menu(std::shared_ptr<Client> c) {
std::vector<S_QuestMenuEntry_DC_GC_A2_A4> entries;
auto s = c->require_server_state();
for (const auto& [_, cat] : s->ep3_map_index->all_categories()) {
for (const auto& [_, cat] : s->data->ep3_map_index->all_categories()) {
if (cat->check_visibility_flag(vis_flag)) {
auto& e = entries.emplace_back();
e.menu_id = MenuID::QUEST_CATEGORIES_EP1_EP3_EP4;
@@ -1669,7 +1669,7 @@ void send_ep3_download_quest_menu(std::shared_ptr<Client> c, uint32_t category_i
: Episode3::MapIndex::VisibilityFlag::ONLINE_FINAL;
auto s = c->require_server_state();
auto category = s->ep3_map_index->category_for_id(category_id);
auto category = s->data->ep3_map_index->category_for_id(category_id);
if (!category->check_visibility_flag(vis_flag)) {
throw std::runtime_error("category is not visible to this client");
}
@@ -1873,7 +1873,7 @@ static void send_join_spectator_team(std::shared_ptr<Client> c, std::shared_ptr<
auto& p = cmd.players[z];
populate_lobby_data_for_client(p.lobby_data, wc, c);
p.inventory = wc_p->inventory;
p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
p.inventory.encode_for_client(c->version(), s->data->item_parameter_table_for_encode(c->version()));
p.disp = wc_p->disp.to_v123<false>(c->language(), p.inventory.language);
p.disp.enforce_lobby_join_limits_for_version(c->version());
@@ -1887,7 +1887,7 @@ static void send_join_spectator_team(std::shared_ptr<Client> c, std::shared_ptr<
: wc_p->disp.stats.level.load();
e.name_color = wc_p->disp.visual.sh.name_color;
uint32_t name_color = s->name_color_for_client(wc);
uint32_t name_color = s->data->name_color_for_client(wc);
if (name_color) {
p.disp.visual.sh.name_color = name_color;
e.name_color = name_color;
@@ -1917,7 +1917,7 @@ static void send_join_spectator_team(std::shared_ptr<Client> c, std::shared_ptr<
auto& p = cmd.players[client_id];
p.lobby_data = entry.lobby_data;
p.inventory = entry.inventory;
p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
p.inventory.encode_for_client(c->version(), s->data->item_parameter_table_for_encode(c->version()));
p.disp = entry.disp;
p.disp.enforce_lobby_join_limits_for_version(c->version());
@@ -1956,7 +1956,7 @@ static void send_join_spectator_team(std::shared_ptr<Client> c, std::shared_ptr<
: other_p->disp.stats.level.load();
cmd_e.name_color = other_p->disp.visual.sh.name_color;
uint32_t name_color = s->name_color_for_client(other_c);
uint32_t name_color = s->data->name_color_for_client(other_c);
if (name_color) {
cmd_p.disp.visual.sh.name_color = name_color;
cmd_e.name_color = name_color;
@@ -2066,11 +2066,11 @@ void send_join_game(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l) {
auto other_p = lc->character_file();
auto& cmd_p = cmd.players_ep3[x];
cmd_p.inventory = other_p->inventory;
cmd_p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
cmd_p.inventory.encode_for_client(c->version(), s->data->item_parameter_table_for_encode(c->version()));
cmd_p.disp = convert_player_disp_data<PlayerDispDataV123>(
other_p->disp, c->language(), other_p->inventory.language);
cmd_p.disp.enforce_lobby_join_limits_for_version(c->version());
uint32_t name_color = s->name_color_for_client(lc);
uint32_t name_color = s->data->name_color_for_client(lc);
if (name_color) {
cmd_p.disp.visual.sh.name_color = name_color;
}
@@ -2180,13 +2180,13 @@ void send_join_lobby_t(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std:
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
e.inventory.encode_for_client(c->version(), s->data->item_parameter_table_for_encode(c->version()));
if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) {
e.disp = convert_player_disp_data<DispDataT>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
} else {
e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version());
uint32_t name_color = s->name_color_for_client(lc);
uint32_t name_color = s->data->name_color_for_client(lc);
if (name_color) {
e.disp.visual.sh.name_color = name_color;
if (is_v1_or_v2(c->version())) {
@@ -2253,10 +2253,10 @@ void send_join_lobby_xb(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
e.inventory.encode_for_client(c->version(), s->data->item_parameter_table_for_encode(c->version()));
e.disp = convert_player_disp_data<PlayerDispDataV123>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version());
uint32_t name_color = s->name_color_for_client(lc);
uint32_t name_color = s->data->name_color_for_client(lc);
if (name_color) {
e.disp.visual.sh.name_color = name_color;
}
@@ -2301,13 +2301,13 @@ void send_join_lobby_dc_nte(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l,
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
e.inventory.encode_for_client(c->version(), s->data->item_parameter_table_for_encode(c->version()));
if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) {
e.disp = convert_player_disp_data<PlayerDispDataV123>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
} else {
e.disp = convert_player_disp_data<PlayerDispDataV123>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version());
uint32_t name_color = s->name_color_for_client(lc);
uint32_t name_color = s->data->name_color_for_client(lc);
if (name_color) {
e.disp.visual.sh.name_color = name_color;
e.disp.visual.sh.compute_name_color_checksum();
@@ -2459,7 +2459,7 @@ asio::awaitable<GetPlayerInfoResult> send_get_player_info(std::shared_ptr<Client
}
try {
auto s = c->require_server_state();
auto fn = s->client_functions->get("GetExtendedPlayerInfo", c->specific_version);
auto fn = s->data->client_functions->get("GetExtendedPlayerInfo", c->specific_version);
send_function_call(c->channel, c->enabled_flags, fn);
c->function_call_response_queue.emplace_back(std::make_shared<AsyncPromise<C_ExecuteCodeResult_B3>>());
full_req_sent = true;
@@ -2486,7 +2486,7 @@ void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemDa
}
cmd.target_client_id = c->lobby_client_id;
cmd.item_count = items.size();
auto item_parameter_table = s->item_parameter_table_for_encode(c->version());
auto item_parameter_table = s->data->item_parameter_table_for_encode(c->version());
for (size_t x = 0; x < items.size(); x++) {
cmd.item_datas[x] = items[x];
cmd.item_datas[x].encode_for_version(c->version(), item_parameter_table);
@@ -2767,7 +2767,7 @@ void send_game_item_state(std::shared_ptr<Client> c) {
fi.room_id = 0;
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
fi.item = item->data;
fi.item.encode_for_version(c->version(), s->item_parameter_table_for_encode(c->version()));
fi.item.encode_for_version(c->version(), s->data->item_parameter_table_for_encode(c->version()));
floor_items_w.put(fi);
decompressed_header.floor_item_count_per_floor.at(floor)++;
@@ -2792,7 +2792,7 @@ void send_game_item_state(std::shared_ptr<Client> c) {
}
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x4F, 0x56, 0x5D);
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, item->pos, item->data}, 0};
cmd.item_data.encode_for_version(c->version(), s->item_parameter_table_for_encode(c->version()));
cmd.item_data.encode_for_version(c->version(), s->data->item_parameter_table_for_encode(c->version()));
w.put(cmd);
}
}
@@ -3080,7 +3080,7 @@ void send_drop_item_to_channel(
uint8_t subcommand = get_pre_v1_subcommand(ch->version, 0x51, 0x58, 0x5F);
G_DropItem_PC_V3_BB_6x5F cmd = {
{{subcommand, 0x0B, 0x0000}, {floor, source_type, entity_index, pos, 0, 0, item}}, 0};
cmd.item.item.encode_for_version(ch->version, s->item_parameter_table_for_encode(ch->version));
cmd.item.item.encode_for_version(ch->version, s->data->item_parameter_table_for_encode(ch->version));
ch->send(0x60, 0x00, &cmd, sizeof(cmd));
}
}
@@ -3093,7 +3093,7 @@ void send_drop_stacked_item_to_channel(
const VectorXZF& pos) {
uint8_t subcommand = get_pre_v1_subcommand(ch->version, 0x4F, 0x56, 0x5D);
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, pos, item}, 0};
cmd.item_data.encode_for_version(ch->version, s->item_parameter_table_for_encode(ch->version));
cmd.item_data.encode_for_version(ch->version, s->data->item_parameter_table_for_encode(ch->version));
ch->send(0x60, 0x00, &cmd, sizeof(cmd));
}
@@ -3303,8 +3303,8 @@ void send_ep3_card_list_update(std::shared_ptr<Client> c) {
if (!c->check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) {
auto s = c->require_server_state();
const auto& data = (c->version() == Version::GC_EP3_NTE)
? s->ep3_card_index_trial->get_compressed_definitions()
: s->ep3_card_index->get_compressed_definitions();
? s->data->ep3_card_index_trial->get_compressed_definitions()
: s->data->ep3_card_index->get_compressed_definitions();
phosg::StringWriter w;
w.put_u32l(data.size());
@@ -3326,8 +3326,8 @@ void send_ep3_media_update(std::shared_ptr<Client> c, uint32_t type, uint32_t wh
void send_ep3_rank_update(std::shared_ptr<Client> c) {
auto s = c->require_server_state();
uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_current_meseta;
uint32_t total_meseta_earned = s->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_total_meseta_earned;
uint32_t current_meseta = s->data->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_current_meseta;
uint32_t total_meseta_earned = s->data->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_total_meseta_earned;
S_RankUpdate_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF};
send_command_t(c, 0xB7, 0x00, cmd);
}
@@ -3383,7 +3383,7 @@ void send_ep3_confirm_tournament_entry(
if (tourn) {
auto s = c->require_server_state();
cmd.tournament_name.encode(tourn->get_name(), c->language());
cmd.server_name.encode(s->name, c->language());
cmd.server_name.encode(s->data->name, c->language());
// TODO: Fill this in appropriately when we support scheduled start times
cmd.start_time.encode("Unknown", c->language());
auto& teams = tourn->all_teams();
@@ -3690,7 +3690,7 @@ void send_ep3_set_tournament_player_decks_t(std::shared_ptr<Client> c) {
add_entries_for_team(match->preceding_b->winner_team, 2);
if ((c->version() != Version::GC_EP3_NTE) &&
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
!(s->data->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
}
@@ -3751,14 +3751,14 @@ void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_
cmd.winner_team_id = (match->preceding_b->winner_team == match->winner_team);
cmd.meseta_amount = meseta_reward;
cmd.meseta_reward_text.encode("You got %s meseta!", Language::ENGLISH);
if ((lc->version() != Version::GC_EP3_NTE) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if ((lc->version() != Version::GC_EP3_NTE) && !(s->data->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
}
send_command_t(lc, 0xC9, 0x00, cmd);
}
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
if (s->data->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
send_text_message_fmt(l, "$C5TOURN/{:X}/{} WIN {}",
tourn->get_menu_item_id(), match->round_num,
match->winner_team == match->preceding_a->winner_team ? 'A' : 'B');
@@ -3788,7 +3788,7 @@ void send_ep3_update_game_metadata(std::shared_ptr<Lobby> l) {
cmd.total_spectators = total_spectators;
for (auto c : l->clients) {
if (c) {
if ((c->version() == Version::GC_EP3) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if ((c->version() == Version::GC_EP3) && !(s->data->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key);
@@ -3823,7 +3823,7 @@ void send_ep3_update_game_metadata(std::shared_ptr<Lobby> l) {
cmd.text.encode(text, Language::ENGLISH);
for (auto c : watcher_l->clients) {
if (c) {
if ((c->version() == Version::GC_EP3) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
if ((c->version() == Version::GC_EP3) && !(s->data->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key);
@@ -3890,7 +3890,7 @@ void send_quest_file_chunk(
c->log.info_f("Sending quest file chunk {}:{}", filename, chunk_index);
const auto& s = c->require_server_state();
c->channel->send(is_download_quest ? 0xA7 : 0x13, chunk_index, &cmd, sizeof(cmd), s->hide_download_commands);
c->channel->send(is_download_quest ? 0xA7 : 0x13, chunk_index, &cmd, sizeof(cmd), s->data->hide_download_commands);
}
template <typename CommandT>
@@ -4080,33 +4080,33 @@ bool send_ep3_start_tournament_deck_select_if_all_clients_ready(std::shared_ptr<
void send_ep3_card_auction(std::shared_ptr<Lobby> l) {
auto s = l->require_server_state();
if ((s->ep3_card_auction_points == 0) ||
(s->ep3_card_auction_min_size == 0) ||
(s->ep3_card_auction_max_size == 0)) {
if ((s->data->ep3_card_auction_points == 0) ||
(s->data->ep3_card_auction_min_size == 0) ||
(s->data->ep3_card_auction_max_size == 0)) {
throw std::runtime_error("card auctions are not configured on this server");
}
uint16_t num_cards;
if (s->ep3_card_auction_min_size == s->ep3_card_auction_max_size) {
num_cards = s->ep3_card_auction_min_size;
if (s->data->ep3_card_auction_min_size == s->data->ep3_card_auction_max_size) {
num_cards = s->data->ep3_card_auction_min_size;
} else {
num_cards = s->ep3_card_auction_min_size +
(phosg::random_object<uint16_t>() % (s->ep3_card_auction_max_size - s->ep3_card_auction_min_size + 1));
num_cards = s->data->ep3_card_auction_min_size +
(phosg::random_object<uint16_t>() % (s->data->ep3_card_auction_max_size - s->data->ep3_card_auction_min_size + 1));
}
num_cards = std::min<uint16_t>(num_cards, 0x14);
auto card_index = l->is_ep3_nte() ? s->ep3_card_index_trial : s->ep3_card_index;
auto card_index = l->is_ep3_nte() ? s->data->ep3_card_index_trial : s->data->ep3_card_index;
uint64_t distribution_size = 0;
for (const auto& e : s->ep3_card_auction_pool) {
for (const auto& e : s->data->ep3_card_auction_pool) {
distribution_size += e.probability;
}
S_StartCardAuction_Ep3_EF cmd;
cmd.points_available = s->ep3_card_auction_points;
cmd.points_available = s->data->ep3_card_auction_points;
for (size_t z = 0; z < num_cards; z++) {
uint64_t v = phosg::random_object<uint64_t>() % distribution_size;
for (const auto& e : s->ep3_card_auction_pool) {
for (const auto& e : s->data->ep3_card_auction_pool) {
if (v >= e.probability) {
v -= e.probability;
} else {
+48 -2046
View File
File diff suppressed because it is too large Load Diff
+16 -372
View File
@@ -15,6 +15,7 @@
#include "CommonItemSet.hh"
#include "DNSServer.hh"
#include "DOLFileIndex.hh"
#include "DataIndex.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh"
#include "GSLArchive.hh"
@@ -37,291 +38,28 @@ class GameServer;
class IPStackSimulator;
class HTTPServer;
struct PortConfiguration {
std::string name;
std::string addr; // Blank = listen on all interfaces (default)
uint16_t port;
Version version;
ServerBehavior behavior;
};
class ServerState : public std::enable_shared_from_this<ServerState> {
public:
std::shared_ptr<DataIndex> data;
struct CheatFlags {
// This structure describes which behaviors are considered cheating (that is, require cheat mode to be enabled or the
// user to have the CHEAT_ANYWHERE account flag). A false value here means that that particular behavior is NOT
// cheating, so cheat mode is NOT required.
bool create_items = true;
bool edit_section_id = true;
bool edit_stats = true;
bool ep3_replace_assist = true;
bool ep3_unset_field_character = true;
bool infinite_hp_tp = true;
bool fast_kills = true;
bool insufficient_minimum_level = true;
bool override_random_seed = true;
bool override_section_id = true;
bool override_variations = true;
bool proxy_override_drops = true;
bool reset_materials = false;
bool warp = true;
CheatFlags() = default;
explicit CheatFlags(const phosg::JSON& json);
};
struct BBStreamFile {
struct Entry {
uint32_t offset;
uint32_t size;
uint32_t checksum; // crc32
std::string filename;
};
std::vector<Entry> entries;
std::string data;
};
struct ServerState : public std::enable_shared_from_this<ServerState> {
enum class RunShellBehavior {
DEFAULT = 0,
ALWAYS,
NEVER,
};
enum class BehaviorSwitch {
OFF = 0,
OFF_BY_DEFAULT,
ON_BY_DEFAULT,
ON,
};
static inline bool behavior_enabled(BehaviorSwitch b) {
return (b == BehaviorSwitch::ON_BY_DEFAULT) || (b == BehaviorSwitch::ON);
}
static inline bool behavior_can_be_overridden(BehaviorSwitch b) {
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
}
uint64_t creation_time;
std::shared_ptr<asio::io_context> io_context;
std::shared_ptr<asio::thread_pool> thread_pool;
std::string config_filename;
std::shared_ptr<const phosg::JSON> config_json;
bool one_time_config_loaded = false;
bool default_lobbies_created = false;
bool is_replay = false;
size_t num_worker_threads = 0;
std::unique_ptr<asio::thread_pool> thread_pool;
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;
std::string dns_server_addr;
uint16_t dns_server_port = 0;
std::vector<std::string> ip_stack_addresses;
std::vector<std::string> ppp_stack_addresses;
std::vector<std::string> ppp_raw_addresses;
std::vector<std::string> http_addresses;
uint64_t client_ping_interval_usecs = 30000000;
uint64_t client_idle_timeout_usecs = 60000000;
uint64_t patch_client_idle_timeout_usecs = 300000000;
bool is_debug = false;
bool ip_stack_debug = false;
bool allow_unregistered_users = false;
bool allow_pc_nte = false;
bool use_temp_accounts_for_prototypes = true;
bool allow_same_account_concurrent_logins = true;
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
bool enable_chat_commands = true;
char chat_command_sentinel = '\0'; // 0 = default (@ on 11/2000; $ on all other versions)
size_t num_backup_character_slots = 16;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint32_t client_customization_name_color = 0x00000000;
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;
ServerDropMode default_drop_mode_v1_v2_normal = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v1_v2_battle = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v1_v2_challenge = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_normal = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_battle = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v3_challenge = ServerDropMode::CLIENT;
ServerDropMode default_drop_mode_v4_normal = ServerDropMode::SERVER_SHARED;
ServerDropMode default_drop_mode_v4_battle = ServerDropMode::SERVER_SHARED;
ServerDropMode default_drop_mode_v4_challenge = ServerDropMode::SERVER_SHARED;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
std::unordered_map<std::string, std::pair<uint8_t, uint32_t>> quest_counter_fields; // For $qfread command
uint64_t persistent_game_idle_timeout_usecs = 0;
std::unordered_map<uint32_t, int64_t> enable_send_function_call_quest_numbers;
bool enable_v3_v4_protected_subcommands = false;
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;
bool censor_credentials = true;
RunShellBehavior run_shell_behavior = RunShellBehavior::DEFAULT;
BehaviorSwitch cheat_mode_behavior = BehaviorSwitch::OFF_BY_DEFAULT;
bool default_switch_assist_enabled = false;
bool use_game_creator_section_id = false;
bool use_psov2_rand_crypt = false; // Used in some tests
bool use_legacy_item_random_behavior = false; // Used in some tests
bool rare_notifs_enabled_for_client_drops = false;
bool default_rare_notifs_enabled_v1_v2 = false;
bool default_rare_notifs_enabled_v3_v4 = false;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_game_for_item_primary_identifiers_v4;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v1_v2;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v3;
std::unordered_set<uint32_t> notify_server_for_item_primary_identifiers_v4;
bool notify_server_for_max_level_achieved = false;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const parray<uint8_t, 0x16C>> bb_default_keyboard_config;
std::shared_ptr<const parray<uint8_t, 0x38>> bb_default_joystick_config;
std::shared_ptr<const ClientFunctionIndex> client_functions;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
std::unordered_map<uint64_t, std::shared_ptr<const MapFile>> map_file_for_source_hash;
std::map<uint32_t, std::array<std::shared_ptr<const MapFile>, NUM_VERSIONS>> map_files_for_free_play_key;
std::unordered_map<uint64_t, std::shared_ptr<const SuperMap>> supermap_for_source_hash_sum;
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermap_for_free_play_key;
std::shared_ptr<const RoomLayoutIndex> room_layout_index;
std::shared_ptr<const BBStreamFile> bb_stream_file;
std::shared_ptr<const DOLFileIndex> dol_file_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index;
std::shared_ptr<const Episode3::CardIndex> ep3_card_index_trial;
std::shared_ptr<const Episode3::MapIndex> ep3_map_index;
std::shared_ptr<const Episode3::COMDeckIndex> ep3_com_deck_index;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_default_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_ex_values;
std::shared_ptr<const G_SetEXResultValues_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> quest_index;
std::shared_ptr<const LevelTable> level_table_v1_v2;
std::shared_ptr<const LevelTable> level_table_v3;
std::shared_ptr<const LevelTable> level_table_v4;
std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl;
std::unordered_map<std::string, std::shared_ptr<const CommonItemSet>> common_item_sets;
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
std::shared_ptr<const ArmorShopRandomSet> armor_random_set;
std::shared_ptr<const ToolShopRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponShopRandomSet>, 4> weapon_random_sets; // Keyed on difficulty
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::shared_ptr<const ItemTranslationTable> item_translation_table;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
size_t bb_max_bank_items = 200;
size_t bb_max_bank_meseta = 999999;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_dc_nte;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_dc_11_2000;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v1;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v2;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v3;
std::shared_ptr<const MagMetadataTable> mag_metadata_table_v4;
std::shared_ptr<const TextIndex> text_index;
std::array<std::shared_ptr<const ItemNameIndex>, NUM_VERSIONS> item_name_indexes;
std::shared_ptr<const WordSelectTable> word_select_table;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables;
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
std::array<std::shared_ptr<const MapState::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates_challenge;
std::array<std::array<size_t, 4>, 3> min_levels_v1_v2; // Indexed as [episode][difficulty]
std::array<std::array<size_t, 4>, 3> min_levels_v3; // Indexed as [episode][difficulty]
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
std::unordered_set<std::string> bb_required_patches;
std::unordered_set<std::string> auto_patches;
CheatFlags cheat_flags;
struct QuestF960Result {
uint32_t meseta_cost = 0;
uint32_t base_probability = 0;
uint32_t probability_upgrade = 0;
std::array<std::vector<ItemData>, 7> results;
QuestF960Result() = default;
QuestF960Result(
const phosg::JSON& json, std::shared_ptr<const ItemNameIndex> name_index, const ItemData::StackLimits& limits);
};
// Indexed as [type][difficulty][random_choice]
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
std::vector<QuestF960Result> quest_F960_success_results;
QuestF960Result quest_F960_failure_results;
float bb_global_exp_multiplier = 1.0f;
float exp_share_multiplier = 0.5f;
float server_global_drop_rate_multiplier = 1.0f;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
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::vector<CardAuctionPoolEntry> ep3_card_auction_pool;
std::array<std::vector<uint16_t>, 5> ep3_trap_card_ids;
struct Ep3LobbyBannerEntry {
uint32_t type = 1;
uint32_t which; // See B9 documentation in CommandFormats.hh
std::string data;
};
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
std::shared_ptr<AccountIndex> account_index;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<TeamIndex> team_index;
phosg::JSON team_reward_defs_json;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
std::shared_ptr<const std::vector<std::string>> information_contents_v2;
std::shared_ptr<const std::vector<std::string>> information_contents_v3;
std::shared_ptr<const Menu> proxy_destinations_menu_dc;
std::shared_ptr<const Menu> proxy_destinations_menu_pc;
std::shared_ptr<const Menu> proxy_destinations_menu_gc;
std::shared_ptr<const Menu> proxy_destinations_menu_xb;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
std::optional<std::pair<std::string, uint16_t>> proxy_destination_patch;
std::optional<std::pair<std::string, uint16_t>> proxy_destination_bb;
std::string welcome_message;
std::string pc_patch_server_message;
std::string bb_patch_server_message;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
std::vector<uint32_t> client_customization_public_lobby_search_order;
std::atomic<int32_t> next_lobby_id = 1;
uint8_t pre_lobby_event = 0;
int32_t ep3_menu_song = -1;
std::unordered_map<uint32_t, std::shared_ptr<Client>> client_for_account;
std::map<std::string, uint32_t> all_addresses;
uint32_t local_address = 0;
uint32_t external_address = 0;
bool proxy_allow_save_files = true;
std::shared_ptr<IPStackSimulator> ip_stack_simulator;
std::shared_ptr<DNSServer> dns_server;
std::shared_ptr<GameServer> game_server;
@@ -329,11 +67,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::unordered_map<uint32_t, ProxySession::PersistentConfig> proxy_persistent_configs;
explicit ServerState(const std::string& config_filename = "", bool is_replay = false);
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
static std::shared_ptr<ServerState> create_shared(std::shared_ptr<DataIndex> data_index, bool is_replay);
std::shared_ptr<ServerState> clone_shared();
void add_client_to_available_lobby(std::shared_ptr<Client> c, bool allow_games);
void remove_client_from_lobby(std::shared_ptr<Client> c);
@@ -343,8 +78,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool send_join_notification = true,
ssize_t required_client_id = -1);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l,
std::shared_ptr<Client> joining_client);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
std::vector<std::shared_ptr<Lobby>> all_lobbies();
@@ -356,110 +90,20 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr, uint64_t account_id = 0, std::shared_ptr<Lobby> l = nullptr);
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
uint16_t game_server_port_for_version(Version v) const;
std::shared_ptr<const Menu> information_menu(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, Difficulty difficulty) const;
inline std::shared_ptr<const WeaponShopRandomSet> weapon_random_set(Difficulty difficulty) const {
return this->weapon_random_sets.at(static_cast<size_t>(difficulty));
}
inline std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates(Difficulty difficulty) const {
return this->rare_enemy_rates_by_difficulty.at(static_cast<size_t>(difficulty));
}
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const MagMetadataTable> mag_metadata_table(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
std::string describe_item(Version version, const ItemData& item, uint8_t flags = 0) const;
ItemData parse_item_description(Version version, const std::string& description) const;
std::shared_ptr<const CommonItemSet> common_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
std::shared_ptr<const RareItemSet> rare_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
return this->public_lobby_search_order(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
if (is_client_customization && this->client_customization_name_color) {
return this->client_customization_name_color;
}
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
}
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
return this->name_color_for_client(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
size_t default_min_level_for_game(Version version, Episode episode, Difficulty difficulty) const;
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file(const std::string& patch_index_filename) const;
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
std::pair<std::string, uint16_t> parse_port_spec(const phosg::JSON& json) const;
std::vector<PortConfiguration> parse_port_configuration(const phosg::JSON& json) const;
static constexpr uint32_t free_play_key(
Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities) {
return (static_cast<uint32_t>(episode) << 28) |
(static_cast<uint32_t>(mode) << 26) |
(static_cast<uint32_t>(difficulty) << 24) |
(static_cast<uint32_t>(floor) << 16) |
(static_cast<uint32_t>(layout) << 8) |
(static_cast<uint32_t>(entities) << 0);
}
std::shared_ptr<const SuperMap> get_free_play_supermap(
Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities);
std::vector<std::shared_ptr<const SuperMap>> supermaps_for_variations(
Episode episode, GameMode mode, Difficulty difficulty, const Variations& variations);
void create_default_lobbies();
void collect_network_addresses();
void load_config_early();
void load_config_late();
void load_bb_private_keys();
void load_bb_system_defaults();
void load_accounts();
void load_teams();
void load_patch_indexes();
void load_maps();
void load_battle_params();
void load_level_tables();
void load_text_index();
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt,
std::shared_ptr<const ItemData::StackLimits> limits,
std::shared_ptr<const TextIndex> text_index) const;
void load_item_name_indexes();
void load_drop_tables();
void load_item_definitions();
void load_set_data_tables();
void load_word_select_table();
void load_ep3_cards();
void load_ep3_maps(bool raise_on_any_failure = false);
void load_ep3_tournament_state();
void load_quest_index(bool raise_on_any_failure = false);
void compile_functions(bool raise_on_any_failure = false);
void load_dol_files();
void generate_bb_stream_file();
void load_all(bool enable_thread_pool);
void update_default_lobby_events_from_config();
void reset_between_replays();
void disconnect_all_banned_clients();
protected:
ServerState() = default;
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
};
+31 -26
View File
@@ -185,51 +185,55 @@ ShellCommand c_reload(
auto types = phosg::split(args.args, ' ');
for (const auto& type : types) {
if (type == "all") {
args.s->load_all(true);
args.s->data->load_all();
args.s->disconnect_all_banned_clients();
args.s->update_default_lobby_events_from_config();
} else if (type == "bb-keys") {
args.s->load_bb_private_keys();
args.s->data->load_bb_private_keys();
} else if (type == "accounts") {
args.s->load_accounts();
} else if (type == "maps") {
args.s->load_maps();
args.s->data->load_maps();
} else if (type == "patch-files") {
args.s->load_patch_indexes();
args.s->data->load_patch_indexes();
} else if (type == "ep3-cards") {
args.s->load_ep3_cards();
args.s->data->load_ep3_cards();
} else if (type == "ep3-maps") {
args.s->load_ep3_maps();
args.s->data->load_ep3_maps();
} else if (type == "ep3-tournaments") {
args.s->load_ep3_tournament_state();
} else if (type == "functions") {
args.s->compile_functions();
args.s->data->compile_functions();
} else if (type == "dol-files") {
args.s->load_dol_files();
args.s->data->load_dol_files();
} else if (type == "set-tables") {
args.s->load_set_data_tables();
args.s->data->load_set_data_tables();
} else if (type == "battle-params") {
args.s->load_battle_params();
args.s->generate_bb_stream_file();
args.s->data->load_battle_params();
args.s->data->generate_bb_stream_file();
} else if (type == "level-tables") {
args.s->load_level_tables();
args.s->generate_bb_stream_file();
args.s->data->load_level_tables();
args.s->data->generate_bb_stream_file();
} else if (type == "text-index") {
args.s->load_text_index();
args.s->data->load_text_index();
} else if (type == "word-select") {
args.s->load_word_select_table();
args.s->data->load_word_select_table();
} else if (type == "item-definitions") {
args.s->load_item_definitions();
args.s->generate_bb_stream_file();
args.s->data->load_item_definitions();
args.s->data->generate_bb_stream_file();
} else if (type == "item-name-index") {
args.s->load_item_name_indexes();
args.s->data->load_item_name_indexes();
} else if (type == "drop-tables") {
args.s->load_drop_tables();
args.s->data->load_drop_tables();
} else if (type == "config") {
args.s->load_config_early();
args.s->load_config_late();
args.s->data->load_config_early();
args.s->data->load_config_late();
args.s->disconnect_all_banned_clients();
args.s->update_default_lobby_events_from_config();
} else if (type == "teams") {
args.s->load_teams();
} else if (type == "quests") {
args.s->load_quest_index();
args.s->data->load_quest_index();
} else {
throw std::runtime_error("invalid data type: " + type);
}
@@ -663,7 +667,7 @@ ShellCommand c_announce_mail(
"announce-mail", "announce-mail MESSAGE\n\
Send an announcement message via Simple Mail to all players.",
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
send_simple_mail(args.s, 0, args.s->name, args.args);
send_simple_mail(args.s, 0, args.s->data->name, args.args);
co_return std::deque<std::string>{};
});
@@ -698,7 +702,7 @@ ShellCommand c_create_tournament(
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string name = get_quoted_string(args.args);
std::string map_name = get_quoted_string(args.args);
auto map = args.s->ep3_map_index->map_for_name(map_name);
auto map = args.s->data->ep3_map_index->map_for_name(map_name);
uint32_t num_teams = std::stoul(get_quoted_string(args.args), nullptr, 0);
Episode3::Rules rules;
rules.set_defaults();
@@ -1089,13 +1093,14 @@ ShellCommand c_create_item(
throw std::runtime_error("proxy session is not game leader");
}
ItemData item = args.s->parse_item_description(c->version(), args.args);
ItemData item = args.s->data->parse_item_description(c->version(), args.args);
item.id = phosg::random_object<uint32_t>() | 0x80000000;
send_drop_stacked_item_to_channel(args.s, c->channel, item, c->floor, c->pos);
send_drop_stacked_item_to_channel(args.s, c->proxy_session->server_channel, item, c->floor, c->pos);
std::string name = args.s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
std::string name = args.s->data->describe_item(
c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
send_text_message(c->channel, "$C7Item created:\n" + name);
co_return std::deque<std::string>{};
});
+7 -3
View File
@@ -27,8 +27,10 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
case SIGUSR1:
this->log.info_f("Received SIGUSR1; reloading config.json");
try {
this->state->load_config_early();
this->state->load_config_late();
this->state->data->load_config_early();
this->state->data->load_config_late();
this->state->disconnect_all_banned_clients();
this->state->update_default_lobby_events_from_config();
phosg::fwrite_fmt(stderr, "Configuration update complete\n");
} catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
@@ -38,7 +40,9 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
case SIGUSR2:
this->log.info_f("Received SIGUSR2; reloading config.json and all dependencies");
try {
this->state->load_all(true);
this->state->data->load_all();
this->state->disconnect_all_banned_clients();
this->state->update_default_lobby_events_from_config();
phosg::fwrite_fmt(stderr, "Configuration update complete\n");
} catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
+1
View File
@@ -411,6 +411,7 @@ I 72448 2025-05-20 20:08:45 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.
0010 | A6 31 06 C2 00 00 00 00 A0 4C 89 3E | 1 L >
I 72448 2025-05-20 20:08:46 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.16.0.30:64731 (version=DC_11_2000 command=60 flag=00)
0000 | 60 00 10 00 4C 03 00 00 0A 00 00 00 00 40 00 00 | ` L @
### cc @variations 00020311101000212120000000000090
I 72448 2025-05-20 20:09:02 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.16.0.30:64731 (version=DC_11_2000 command=0C flag=03)
0000 | 0C 03 2C 00 00 00 00 00 00 00 00 00 41 41 41 41 | , AAAA
0010 | 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | A