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/DCSerialNumbers.cc
src/DNSServer.cc src/DNSServer.cc
src/DOLFileIndex.cc src/DOLFileIndex.cc
src/DataIndex.cc
src/DownloadSession.cc src/DownloadSession.cc
src/EnemyType.cc src/EnemyType.cc
src/Episode3/AssistServer.cc src/Episode3/AssistServer.cc
@@ -157,17 +158,11 @@ add_dependencies(newserv newserv-Revision-cc)
enable_testing() enable_testing()
file(GLOB LOG_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.txt) file(GLOB LOG_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.txt)
foreach(LOG_TEST_CASE IN ITEMS ${LOG_TEST_CASES}) list(TRANSFORM LOG_TEST_CASES PREPEND "--replay-log=" OUTPUT_VARIABLE LOG_REPLAY_ARGS)
add_test( add_test(
NAME ${LOG_TEST_CASE} NAME "log-replays"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_BINARY_DIR}/newserv --replay-log=${LOG_TEST_CASE} --config=${CMAKE_SOURCE_DIR}/tests/config.json) COMMAND ${CMAKE_BINARY_DIR}/newserv --parallel --config=${CMAKE_SOURCE_DIR}/tests/config.json ${LOG_REPLAY_ARGS})
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})
file(GLOB SCRIPT_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.sh) file(GLOB SCRIPT_TEST_CASES ${CMAKE_SOURCE_DIR}/tests/*.test.sh)
foreach(SCRIPT_TEST_CASE IN ITEMS ${SCRIPT_TEST_CASES}) 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 { void check_cheat_mode_available(bool behavior_is_cheating) const {
if (behavior_is_cheating && if (behavior_is_cheating &&
this->check_permissions && 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))) { (!this->c->login || !this->c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))) {
throw precondition_failed("$C6Cheats are disabled"); 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(); auto s = a.c->require_server_state();
if (anonymous) { if (anonymous) {
if (mail) { if (mail) {
send_simple_mail(s, 0, s->name, a.text); send_simple_mail(s, 0, s->data->name, a.text);
} else { } else {
send_text_or_scrolling_message(s, a.text, a.text); 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 { } else {
dest_character_index = stoull(a.text) - 1; dest_character_index = stoull(a.text) - 1;
if (dest_character_index >= 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->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; 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 // Client sent 61; generate a BB-format player from the information we have and save that instead
if (ch.character) { if (ch.character) {
auto bb_player = PSOBBCharacterFile::create_from_config( 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.version = 4;
bb_player->disp.visual.sh.name_color_checksum = 0x00000000; bb_player->disp.visual.sh.name_color_checksum = 0x00000000;
bb_player->inventory = ch.character->inventory; bb_player->inventory = ch.character->inventory;
// Before V3, player stats can't be correctly computed from other fields because material usage isn't stored // 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. // 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())) { if (is_v1_or_v2(a.c->version())) {
bb_player->disp.stats = ch.character->disp.stats; bb_player->disp.stats = ch.character->disp.stats;
bb_player->import_tethealla_material_usage(level_table); bb_player->import_tethealla_material_usage(level_table);
@@ -578,8 +578,8 @@ ChatCommandDefinition cc_cheat(
auto s = a.c->require_server_state(); auto s = a.c->require_server_state();
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
!a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE) && !a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE) &&
s->cheat_flags.insufficient_minimum_level) { s->data->cheat_flags.insufficient_minimum_level) {
size_t default_min_level = s->default_min_level_for_game(a.c->version(), l->episode, l->difficulty); 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) { if (l->min_level < default_min_level) {
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); send_text_message_fmt(l, "$C6Minimum level set\nto {}", l->min_level + 1);
@@ -603,7 +603,7 @@ ChatCommandDefinition cc_checkchar(
std::vector<bool> flags; std::vector<bool> flags;
flags.emplace_back(false); 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); 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)); flags.emplace_back(std::filesystem::is_regular_file(filename));
} }
@@ -615,8 +615,8 @@ ChatCommandDefinition cc_checkchar(
} else { } else {
size_t index = stoull(a.text, nullptr, 0) - 1; size_t index = stoull(a.text, nullptr, 0) - 1;
if (index >= s->num_backup_character_slots) { if (index >= s->data->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots); throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->data->num_backup_character_slots);
} }
try { try {
@@ -760,7 +760,7 @@ ChatCommandDefinition cc_dropmode(
+[](const Args& a) -> asio::awaitable<void> { +[](const Args& a) -> asio::awaitable<void> {
a.check_is_game(true); a.check_is_game(true);
auto s = a.c->require_server_state(); 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) { if (a.c->proxy_session) {
@@ -881,7 +881,7 @@ ChatCommandDefinition cc_edit(
} }
bool cheats_allowed = (!a.check_permissions || 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)); a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
std::string encoded_args = phosg::tolower(a.text); std::string encoded_args = phosg::tolower(a.text);
@@ -891,28 +891,28 @@ ChatCommandDefinition cc_edit(
try { try {
auto p = a.c->character_file(); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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->disp.stats.level = std::stoul(tokens.at(1)) - 1;
p->recompute_stats(s->level_table(a.c->version()), true); 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->cheat_flags.reset_materials)) { } 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") { if (tokens.at(1) == "reset") {
const auto& which = tokens.at(2); const auto& which = tokens.at(2);
if (which == "power") { if (which == "power") {
@@ -949,7 +949,7 @@ ChatCommandDefinition cc_edit(
} else { } else {
throw precondition_failed("$C6Invalid subcommand"); 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") { } else if (tokens.at(0) == "namecolor") {
p->disp.visual.sh.name_color = std::stoul(tokens.at(1), nullptr, 16); p->disp.visual.sh.name_color = std::stoul(tokens.at(1), nullptr, 16);
} else if (tokens.at(0) == "language" || tokens.at(0) == "lang") { } else if (tokens.at(0) == "language" || tokens.at(0) == "lang") {
@@ -965,7 +965,7 @@ ChatCommandDefinition cc_edit(
sys->language = new_language; sys->language = new_language;
} }
} else if (tokens.at(0) == "secid") { } 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"); throw precondition_failed("$C6You cannot change\nyour Section ID\nafter level 1");
} }
uint8_t secid = section_id_for_name(tokens.at(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.extra_model = npc;
p->disp.visual.sh.validation_flags |= 0x02; 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; uint8_t level = std::stoul(tokens.at(2)) - 1;
if (tokens.at(1) == "all") { if (tokens.at(1) == "all") {
for (size_t x = 0; x < 0x14; x++) { for (size_t x = 0; x < 0x14; x++) {
@@ -1106,7 +1106,7 @@ ChatCommandDefinition cc_exit(
auto s = a.c->require_server_state(); auto s = a.c->require_server_state();
std::shared_ptr<const ClientFunctionIndex::Function> fn; std::shared_ptr<const ClientFunctionIndex::Function> fn;
try { 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&) { } catch (const std::out_of_range&) {
} }
if (fn) { if (fn) {
@@ -1154,7 +1154,7 @@ ChatCommandDefinition cc_infhp(
send_text_message(a.c, "$C6Infinite HP disabled"); send_text_message(a.c, "$C6Infinite HP disabled");
} else { } else {
auto s = a.c->require_server_state(); 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); a.c->set_flag(Client::Flag::INFINITE_HP_ENABLED);
co_await send_remove_negative_conditions(a.c); co_await send_remove_negative_conditions(a.c);
if (a.c->proxy_session) { if (a.c->proxy_session) {
@@ -1202,7 +1202,7 @@ ChatCommandDefinition cc_inftp(
send_text_message(a.c, "$C6Infinite TP disabled"); send_text_message(a.c, "$C6Infinite TP disabled");
} else { } else {
auto s = a.c->require_server_state(); 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); a.c->set_flag(Client::Flag::INFINITE_TP_ENABLED);
send_text_message(a.c, "$C6Infinite TP enabled"); send_text_message(a.c, "$C6Infinite TP enabled");
} }
@@ -1214,7 +1214,7 @@ ChatCommandDefinition cc_item(
+[](const Args& a) -> asio::awaitable<void> { +[](const Args& a) -> asio::awaitable<void> {
a.check_is_game(true); a.check_is_game(true);
auto s = a.c->require_server_state(); 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; ItemData item;
bool was_enqueued = false; bool was_enqueued = false;
@@ -1225,12 +1225,12 @@ ChatCommandDefinition cc_item(
a.check_is_leader(); a.check_is_leader();
if (a.text.starts_with("!")) { 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; a.c->proxy_session->next_drop_item = item;
was_enqueued = true; was_enqueued = true;
} else { } 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; 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); 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 { } else {
auto l = a.c->require_lobby(); 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); item.id = l->generate_item_id(0xFF);
if ((l->drop_mode == ServerDropMode::SERVER_PRIVATE) || (l->drop_mode == ServerDropMode::SERVER_DUPLICATE)) { 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) { if (was_enqueued) {
send_text_message(a.c, "$C7Next item:\n" + name); send_text_message(a.c, "$C7Next item:\n" + name);
} else { } else {
@@ -1340,7 +1340,7 @@ ChatCommandDefinition cc_killcount(
auto s = a.c->require_server_state(); auto s = a.c->require_server_state();
for (size_t z : item_indexes) { for (size_t z : item_indexes) {
const auto& item = p->inventory.items[z]; 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); 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()); 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(); auto l = a.c->require_lobby();
size_t index = stoull(a.text, nullptr, 0) - 1; size_t index = stoull(a.text, nullptr, 0) - 1;
if (index >= s->num_backup_character_slots) { if (index >= s->data->num_backup_character_slots) {
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->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; 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> { 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); co_await prepare_client_for_patches(a.c);
try { 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)); co_await send_function_call(a.c, fn, {}, &char_file, sizeof(CharT));
auto l = a.c->lobby.lock(); auto l = a.c->lobby.lock();
if (l) { if (l) {
@@ -1699,7 +1699,7 @@ ChatCommandDefinition cc_makeobj(
co_await prepare_client_for_patches(a.c); co_await prepare_client_for_patches(a.c);
auto s = a.c->require_server_state(); 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); co_await send_function_call(a.c, fn, label_writes);
}); });
@@ -1760,8 +1760,8 @@ ChatCommandDefinition cc_minlevel(
auto s = a.c->require_server_state(); auto s = a.c->require_server_state();
bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) ||
a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)); a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
if (!cheats_allowed && s->cheat_flags.insufficient_minimum_level) { if (!cheats_allowed && s->data->cheat_flags.insufficient_minimum_level) {
size_t default_min_level = s->default_min_level_for_game(a.c->version(), l->episode, l->difficulty); 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) { if (new_min_level < default_min_level) {
throw precondition_failed("$C6Cannot set minimum\nlevel below {}", default_min_level + 1); 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> { +[](const Args& a) -> asio::awaitable<void> {
a.check_is_game(true); a.check_is_game(true);
auto s = a.c->require_server_state(); 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; 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); size_t limit = FloorDefinition::limit_for_episode(episode);
@@ -1838,7 +1838,7 @@ ChatCommandDefinition cc_patch(
try { try {
auto s = a.c->require_server_state(); 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 // 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) { switch (fn->visibility) {
case ClientFunctionIndex::Function::Visibility::DEBUG_ONLY: case ClientFunctionIndex::Function::Visibility::DEBUG_ONLY:
@@ -2060,7 +2060,7 @@ ChatCommandDefinition cc_qfread(
uint8_t counter_index; uint8_t counter_index;
uint32_t mask; uint32_t mask;
try { 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; counter_index = def.first;
mask = def.second; mask = def.second;
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
@@ -2189,7 +2189,7 @@ ChatCommandDefinition cc_quest(
a.check_is_game(true); a.check_is_game(true);
auto s = a.c->require_server_state(); 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) { if (!q) {
throw precondition_failed("$C6Quest not found"); throw precondition_failed("$C6Quest not found");
} }
@@ -2229,7 +2229,7 @@ ChatCommandDefinition cc_fastkill(
send_text_message(a.c, "$C6Fast kills disabled"); send_text_message(a.c, "$C6Fast kills disabled");
} else { } else {
auto s = a.c->require_server_state(); 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); a.c->set_flag(Client::Flag::FAST_KILLS_ENABLED);
send_text_message(a.c, "$C6Fast 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 s = a.c->require_server_state();
auto l = a.c->require_lobby(); auto l = a.c->require_lobby();
a.check_is_game(false); 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()) { if (a.text.empty()) {
a.c->override_random_seed = -1; a.c->override_random_seed = -1;
@@ -2288,7 +2288,7 @@ ChatCommandDefinition cc_readmem(
std::shared_ptr<const ClientFunctionIndex::Function> fn; std::shared_ptr<const ClientFunctionIndex::Function> fn;
try { try {
auto s = a.c->require_server_state(); 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&) { } catch (const std::out_of_range&) {
throw precondition_failed("Invalid patch name"); throw precondition_failed("Invalid patch name");
} }
@@ -2332,7 +2332,7 @@ ChatCommandDefinition cc_savefiles(
a.check_is_proxy(true); a.check_is_proxy(true);
auto s = a.c->require_server_state(); 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"); send_text_message(a.c, "$C6Save files is not\nallowed");
} else if (a.c->check_flag(Client::Flag::PROXY_SAVE_FILES)) { } else if (a.c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
a.c->clear_flag(Client::Flag::PROXY_SAVE_FILES); a.c->clear_flag(Client::Flag::PROXY_SAVE_FILES);
@@ -2406,7 +2406,7 @@ ChatCommandDefinition cc_secid(
{"$secid"}, {"$secid"},
+[](const Args& a) -> asio::awaitable<void> { +[](const Args& a) -> asio::awaitable<void> {
auto s = a.c->require_server_state(); 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; uint8_t new_override_section_id;
if (a.text.empty()) { if (a.text.empty()) {
@@ -2439,7 +2439,7 @@ ChatCommandDefinition cc_setassist(
a.check_is_game(true); a.check_is_game(true);
a.check_is_ep3(true); a.check_is_ep3(true);
auto s = a.c->require_server_state(); 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(); auto l = a.c->require_lobby();
if (l->episode != Episode::EP3) { if (l->episode != Episode::EP3) {
@@ -2486,7 +2486,7 @@ ChatCommandDefinition cc_server_info(
{"$si"}, {"$si"},
+[](const Args& a) -> asio::awaitable<void> { +[](const Args& a) -> asio::awaitable<void> {
auto s = a.c->require_server_state(); 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, send_text_message_fmt(a.c,
"Uptime: $C6{}$C7\nLobbies: $C6{}$C7\nClients: $C6{}$C7(g) $C6{}$C7(p)", "Uptime: $C6{}$C7\nLobbies: $C6{}$C7\nClients: $C6{}$C7(g) $C6{}$C7(p)",
uptime_str, uptime_str,
@@ -2847,7 +2847,7 @@ ChatCommandDefinition cc_unset(
a.check_is_game(true); a.check_is_game(true);
a.check_is_ep3(true); a.check_is_ep3(true);
auto s = a.c->require_server_state(); 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(); auto l = a.c->require_lobby();
if (l->episode != Episode::EP3) { if (l->episode != Episode::EP3) {
throw std::logic_error("non-Ep3 client in Ep3 game"); 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_proxy(false);
a.check_is_game(false); a.check_is_game(false);
auto s = a.c->require_server_state(); 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>(); 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++) { 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) { static void command_warp(const Args& a, bool is_warpall) {
a.check_is_game(true); a.check_is_game(true);
auto s = a.c->require_server_state(); 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); uint32_t floor = std::stoul(a.text, nullptr, 0);
if (!is_warpall && (a.c->floor == floor)) { if (!is_warpall && (a.c->floor == floor)) {
@@ -2968,7 +2968,8 @@ ChatCommandDefinition cc_what(
} else { } else {
auto s = a.c->require_server_state(); auto s = a.c->require_server_state();
send_text_message( 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; co_return;
}); });
@@ -3002,7 +3003,7 @@ static void whatobj_whatene_fn(const Args& a, bool include_objs, bool include_en
VectorXYZF worldspace_pos; VectorXYZF worldspace_pos;
if (l->episode != Episode::EP3) { if (l->episode != Episode::EP3) {
try { 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 // 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; 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&) { } catch (const std::out_of_range&) {
@@ -3141,7 +3142,7 @@ ChatCommandDefinition cc_writemem(
try { try {
auto s = a.c->require_server_state(); 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()}}; 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()); co_await send_function_call(a.c, fn, label_writes, data.data(), data.size());
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
@@ -3181,7 +3182,7 @@ ChatCommandDefinition cc_nativecall(
try { try {
auto s = a.c->require_server_state(); 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); co_await send_function_call(a.c, fn, label_writes);
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
throw precondition_failed("Invalid patch name"); 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 // 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. // for patch clients are generally more annoying than helpful at this point.
auto s = server->get_state(); 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_recv_color = phosg::TerminalFormat::END;
this->channel->terminal_send_color = phosg::TerminalFormat::END; this->channel->terminal_send_color = phosg::TerminalFormat::END;
} else { } else {
@@ -200,7 +200,7 @@ Client::Client(
} }
this->set_flags_for_version(this->version(), -1); 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->set_drop_notification_mode(ItemDropNotificationMode::RARES_ONLY);
} }
this->specific_version = default_specific_version_for_version(this->version(), -1); 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 // 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. // 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->version() == Version::PC_PATCH) || (this->version() == Version::BB_PATCH))) {
this->channel->terminal_recv_color = phosg::TerminalFormat::END; this->channel->terminal_recv_color = phosg::TerminalFormat::END;
this->channel->terminal_send_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() { void Client::reschedule_ping_and_timeout_timers() {
auto s = this->require_server_state(); auto s = this->require_server_state();
if (!is_patch(this->version())) { 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) { this->send_ping_timer.async_wait([this](std::error_code ec) {
if (!ec) { if (!ec) {
this->log.info_f("Sending ping command"); 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) { this->idle_timeout_timer.async_wait([this](std::error_code ec) {
if (!ec) { if (!ec) {
this->log.info_f("Idle timeout expired"); 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, // 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. // delete the permanent account and replace it with a temporary account.
auto s = this->require_server_state(); 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->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->is_temporary = true;
this->login->account->delete_file(); 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)) { if (this->login->account->check_flag(Account::Flag::ALWAYS_ENABLE_CHAT_COMMANDS)) {
return true; 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) { void Client::set_login(std::shared_ptr<Login> login) {
this->login = login; this->login = login;
auto s = this->require_server_state(); 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); auto it = s->client_for_account.find(login->account->account_id);
if ((it != s->client_for_account.end()) && (it->second.get() != this)) { if ((it != s->client_for_account.end()) && (it->second.get() != this)) {
if (it->second->channel) { if (it->second->channel) {
@@ -796,8 +796,8 @@ std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
} }
auto s = this->require_server_state(); auto s = this->require_server_state();
this->bank_data->max_items = s->bb_max_bank_items; this->bank_data->max_items = s->data->bb_max_bank_items;
this->bank_data->max_meseta = s->bb_max_bank_meseta; this->bank_data->max_meseta = s->data->bb_max_bank_meseta;
this->update_bank_data_after_load(this->bank_data); this->update_bank_data_after_load(this->bank_data);
} }
return this->bank_data; return this->bank_data;
@@ -1000,11 +1000,11 @@ void Client::load_all_files() {
if (!this->system_data) { if (!this->system_data) {
this->system_data = std::make_shared<PSOBBBaseSystemFile>(); this->system_data = std::make_shared<PSOBBBaseSystemFile>();
auto s = this->require_server_state(); auto s = this->require_server_state();
if (s->bb_default_keyboard_config) { if (s->data->bb_default_keyboard_config) {
this->system_data->key_config = *s->bb_default_keyboard_config; this->system_data->key_config = *s->data->bb_default_keyboard_config;
} }
if (s->bb_default_joystick_config) { if (s->data->bb_default_joystick_config) {
this->system_data->joystick_config = *s->bb_default_joystick_config; this->system_data->joystick_config = *s->data->bb_default_joystick_config;
} }
this->log.info_f("Created new system data"); this->log.info_f("Created new system data");
} }
@@ -1014,7 +1014,7 @@ void Client::load_all_files() {
} }
auto s = this->require_server_state(); 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(); this->blocked_senders.clear();
for (size_t z = 0; z < this->guild_card_data->blocked_senders.size(); z++) { 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) { 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(); Language lang = this->language();
this->log.info_f("Overriding language fields in save files with {}", name_for_language(lang)); 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) { void Client::update_bank_data_after_load(std::shared_ptr<PlayerBank> bank) {
auto s = this->require_server_state(); 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) { for (auto& item : bank->items) {
if (item.data.is_stackable(*limits)) { if (item.data.is_stackable(*limits)) {
if (item.data.data1[5] != item.amount) { 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++) { for (size_t x = 0; x < p->inventory.num_items; x++) {
const auto& item = p->inventory.items[x]; const auto& item = p->inventory.items[x];
auto hex = item.data.hex(); 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); 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 auto& item = this->bank_data->items[x];
const char* present_token = item.present ? "" : " (missing present flag)"; const char* present_token = item.present ? "" : " (missing present flag)";
auto hex = item.data.hex(); 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); this->log.info_f("[PlayerBank] {:3}: {} ({}) (x{}){}", x, hex, name, item.amount, present_token);
} }
} else { } else {
+4 -5
View File
@@ -14,8 +14,7 @@
#include "NetworkAddresses.hh" #include "NetworkAddresses.hh"
#include "ServerState.hh" #include "ServerState.hh"
DNSServer::DNSServer(std::shared_ptr<ServerState> state) DNSServer::DNSServer(std::shared_ptr<ServerState> state) : state(state) {}
: state(state) {}
void DNSServer::listen(const std::string& addr, int port) { void DNSServer::listen(const std::string& addr, int port) {
if (port == 0) { if (port == 0) {
@@ -62,11 +61,11 @@ asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::
if (bytes < 0x0C) { if (bytes < 0x0C) {
dns_server_log.warning_f("input query too small"); dns_server_log.warning_f("input query too small");
phosg::print_data(stderr, input.data(), bytes); 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); input.resize(bytes);
uint32_t connect_address = is_local_address(sender_addr) uint32_t connect_address = is_local_address(sender_addr)
? this->state->local_address ? this->state->data->local_address
: this->state->external_address; : this->state->data->external_address;
std::string response = this->response_for_query(input, connect_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); 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" #include "IPV4RangeSet.hh"
struct ServerState; class ServerState;
class DNSServer { class DNSServer {
public: 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; struct Lobby;
class Client; class Client;
struct ServerState; class ServerState;
namespace Episode3 { 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<Client> GameServer::create_client(
std::shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) { 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()); 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()) { if (client_sock.is_open()) {
client_sock.close(); client_sock.close();
} }
@@ -129,7 +129,7 @@ std::shared_ptr<Client> GameServer::create_client(
"", "",
phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::FG_YELLOW,
phosg::TerminalFormat::FG_GREEN, phosg::TerminalFormat::FG_GREEN,
this->state->censor_credentials, this->state->data->censor_credentials,
false); false);
auto c = std::make_shared<Client>(this->shared_from_this(), channel, listen_sock->behavior); 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); 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 { this->router.add(HTTPRequest::Method::GET, "/y/clients", [this](ArgsT&&) -> RetT {
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list()); auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
for (const auto& c : this->state->game_server->all_clients()) { 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"; const char* drop_notifications_mode = "unknown";
switch (c->get_drop_notification_mode()) { 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) { for (const auto& [_, l] : this->state->id_to_lobby) {
auto leader = l->clients[l->leader_id]; auto leader = l->clients[l->leader_id];
Version v = leader ? leader->version() : Version::BB_V4; 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(); auto client_ids_json = phosg::JSON::list();
for (size_t z = 0; z < l->max_clients; z++) { for (size_t z = 0; z < l->max_clients; z++) {
@@ -559,17 +559,17 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
lobby_count++; 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({ return phosg::JSON::dict({
{"StartTimeUsecs", this->state->creation_time}, {"StartTimeUsecs", this->state->data->creation_time},
{"StartTime", phosg::format_time(this->state->creation_time)}, {"StartTime", phosg::format_time(this->state->data->creation_time)},
{"UptimeUsecs", uptime_usecs}, {"UptimeUsecs", uptime_usecs},
{"Uptime", phosg::format_duration(uptime_usecs)}, {"Uptime", phosg::format_duration(uptime_usecs)},
{"LobbyCount", lobby_count}, {"LobbyCount", lobby_count},
{"GameCount", game_count}, {"GameCount", game_count},
{"ClientCount", this->state->game_server->all_clients().size() - ProxySession::num_proxy_sessions}, {"ClientCount", this->state->game_server->all_clients().size() - ProxySession::num_proxy_sessions},
{"ProxySessionCount", 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 { 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 { 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 { 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> { 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()); 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 { 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"); uint32_t card_id = args.get_param<uint32_t>("card_id");
try { try {
co_return std::make_shared<phosg::JSON>(index->definition_for_id(card_id)->def.json()); 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 { 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> { 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()); 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(); auto languages_json = phosg::JSON::list();
for (const auto& vm : map->all_versions()) { for (const auto& vm : map->all_versions()) {
if (vm) { 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 { 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> { co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
try { 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"))); auto vm = map->version(language_for_name(args.params.at("language")));
return std::make_shared<phosg::JSON>(vm->map->json(vm->language)); return std::make_shared<phosg::JSON>(vm->map->json(vm->language));
} catch (const std::out_of_range&) { } 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 { 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 { co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> RawResponse {
try { 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"))); auto vm = map->version(language_for_name(args.params.at("language")));
std::string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition)); std::string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
return RawResponse{.content_type = "application/octet-stream", .data = std::move(data)}; 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 { this->router.add(HTTPRequest::Method::GET, "/y/data/common-tables", [this](ArgsT&&) -> RetT {
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list()); 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); ret->emplace_back(it.first);
} }
co_return ret; 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 { this->router.add(HTTPRequest::Method::GET, "/y/data/common-table/:table_name", [this](ArgsT&& args) -> RetT {
try { 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> { 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()); 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 { this->router.add(HTTPRequest::Method::GET, "/y/data/rare-tables", [this](ArgsT&&) -> RetT {
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list()); 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); ret->emplace_back(it.first);
} }
co_return ret; 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 { this->router.add(HTTPRequest::Method::GET, "/y/data/rare-table/:table_name", [this](ArgsT&& args) -> RetT {
try { try {
const auto& table_name = args.params.at("table_name"); 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; std::shared_ptr<const ItemNameIndex> name_index;
if (table_name.ends_with("-v1")) { 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")) { } 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")) { } 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")) { } 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> { 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)); 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 { 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> { 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 { 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"); 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) { if (!q) {
throw HTTPError(404, "Quest does not exist"); 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"); throw std::runtime_error("cannot reschedule idle timeout when simulator is missing");
} }
this->idle_timeout_timer.cancel(); 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) { this->idle_timeout_timer.async_wait([this, sim](std::error_code ec) {
if (!ec) { if (!ec) {
sim->log.info_f("Idle timeout expired on N-{:X}", this->network_id); 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); std::string conn_str = this->str_for_tcp_connection(c, conn);
// Figure out which logical port the connection should go to // Figure out which logical port the connection should go to
auto port_config_it = this->state->number_to_port_config.find(conn->server_port); auto port_config_it = this->state->data->number_to_port_config.find(conn->server_port);
if (port_config_it == this->state->number_to_port_config.end()) { 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); this->log.error_f("TCP connection {} is to undefined port {}", conn_str, conn->server_port);
co_await this->close_tcp_connection(c, conn); co_await this->close_tcp_connection(c, conn);
co_return; co_return;
@@ -1370,20 +1370,20 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
this->shared_from_this(), this->shared_from_this(),
c, c,
conn, conn,
port_config->version, port_config.version,
Language::ENGLISH, Language::ENGLISH,
"", "",
phosg::TerminalFormat::END, phosg::TerminalFormat::END,
phosg::TerminalFormat::END, phosg::TerminalFormat::END,
false, false,
this->state->censor_credentials); this->state->data->censor_credentials);
if (!this->state->game_server.get()) { if (!this->state->game_server.get()) {
this->log.error_f("No server available for TCP connection {}", conn_str); this->log.error_f("No server available for TCP connection {}", conn_str);
co_await this->close_tcp_connection(c, conn); co_await this->close_tcp_connection(c, conn);
co_return; co_return;
} else { } 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); 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<IPSSClient> IPStackSimulator::create_client(
std::shared_ptr<IPSSSocket> listen_sock, asio::ip::tcp::socket&& client_sock) { 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()); 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()) { if (client_sock.is_open()) {
client_sock.close(); 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) // Nothing to do (it should be deleted)
} else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk } 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]); 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) { if (item.data.data1[2] > max_level) {
throw std::runtime_error("technique level too high"); 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)]; 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 // 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]); 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)) { if (is_v4 && (weapon.data.data1[3] >= weapon_def.max_grind)) {
throw std::runtime_error("weapon already at maximum 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]++; 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 // 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; should_delete_item = false;
} else if (primary_identifier == 0x00330000) { } 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 } else if ((primary_identifier & 0xFFFF0000) == 0x030C0000) { // Non-combo mag cells
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)]; 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) { if (evolution_number < 4) {
switch (item.data.data1[2]) { switch (item.data.data1[2]) {
case 0x00: // Cell of MAG 502 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) { } else if ((primary_identifier & 0xFFFF0000) == 0x03150000) {
// Christmas Present, etc. - use unwrap_table + probabilities therein // 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]); auto table = item_parameter_table->get_event_items(item.data.data1[2]);
size_t sum = 0; size_t sum = 0;
for (size_t z = 0; z < table.second; z++) { 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 // 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 // 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. // 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); auto report_item = player->remove_item(item.data.id, 1, stack_limits);
report_item.data1[2] = report_item.get_kill_count(); report_item.data1[2] = report_item.get_kill_count();
player->add_item(report_item, stack_limits); 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; continue;
} }
try { 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); 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) { if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.sh.char_class) {
throw std::runtime_error("item combination requires specific 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) { if (should_delete_item) {
// Allow overdrafting meseta if the client is not BB, since the server isn't informed when meseta is added or // Allow overdrafting meseta if the client is not BB, since the server isn't informed when meseta is added or
// removed from the bank. // 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( apply_mag_feed_result(
player->inventory.items[mag_item_index].data, player->inventory.items[mag_item_index].data,
player->inventory.items[fed_item_index].data, player->inventory.items[fed_item_index].data,
s->item_parameter_table(c->version()), s->data->item_parameter_table(c->version()),
s->mag_metadata_table(c->version()), s->data->mag_metadata_table(c->version()),
player->disp.visual.sh.char_class, player->disp.visual.sh.char_class,
player->disp.visual.sh.section_id, player->disp.visual.sh.section_id,
!is_v1_or_v2(c->version())); !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) { if (this->quest) {
return this->quest->meta.floor_assignments.at(floor).area; 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); 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; effective_section_id = 0x00;
} }
this->item_creator = std::make_shared<ItemCreator>( this->item_creator = std::make_shared<ItemCreator>(
s->common_item_set(logic_version, this->quest), s->data->common_item_set(logic_version, this->quest),
s->rare_item_set(logic_version, this->quest), s->data->rare_item_set(logic_version, this->quest),
s->armor_random_set, s->data->armor_random_set,
s->tool_random_set, s->data->tool_random_set,
s->weapon_random_set(this->difficulty), s->data->weapon_random_set(this->difficulty),
s->tekker_adjustment_set, s->data->tekker_adjustment_set,
s->item_parameter_table(logic_version), s->data->item_parameter_table(logic_version),
s->item_stack_limits(logic_version), s->data->item_stack_limits(logic_version),
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode, (this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
this->difficulty, this->difficulty,
effective_section_id, effective_section_id,
@@ -277,7 +277,7 @@ void Lobby::load_maps() {
} else { } else {
this->log.info_f("Loading free play supermaps"); this->log.info_f("Loading free play supermaps");
auto s = this->require_server_state(); 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->map_state = std::make_shared<MapState>(
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermaps); 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(); bool is_nte = this->is_ep3_nte();
Episode3::Server::Options options = { Episode3::Server::Options options = {
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index, .card_index = is_nte ? s->data->ep3_card_index_trial : s->data->ep3_card_index,
.map_index = s->ep3_map_index, .map_index = s->data->ep3_map_index,
.behavior_flags = s->ep3_behavior_flags, .behavior_flags = s->data->ep3_behavior_flags,
.opt_rand_stream = nullptr, .opt_rand_stream = nullptr,
.rand_crypt = this->rand_crypt, .rand_crypt = this->rand_crypt,
.tournament = tourn, .tournament = tourn,
.trap_card_ids = s->ep3_trap_card_ids, .trap_card_ids = s->data->ep3_trap_card_ids,
.output_queue = nullptr, .output_queue = nullptr,
}; };
if (is_nte) { if (is_nte) {
+1 -1
View File
@@ -20,7 +20,7 @@
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
struct ServerState; class ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> { struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem { 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\ 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", option is given, prints the token table sorted by canonical name.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_patch_indexes(); di->load_patch_indexes();
s->load_text_index(); di->load_text_index();
s->load_word_select_table(); di->load_word_select_table();
Version v; Version v;
try { try {
v = get_cli_version(args); v = get_cli_version(args);
@@ -2086,9 +2086,9 @@ Action a_print_word_select_table(
v = Version::UNKNOWN; v = Version::UNKNOWN;
} }
if (v != Version::UNKNOWN) { if (v != Version::UNKNOWN) {
s->word_select_table->print_index(stdout, v); di->word_select_table->print_index(stdout, v);
} else { } 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", drop-anything rate; the true drop rates are shown in tooltips.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
double rate_factor = args.get<double>("multiply", 1.0); double rate_factor = args.get<double>("multiply", 1.0);
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_text_index(); di->load_text_index();
s->load_item_definitions(); di->load_item_definitions();
s->load_item_name_indexes(); di->load_item_name_indexes();
s->load_drop_tables(); di->load_drop_tables();
std::string input_filename = args.get<std::string>(1, false); std::string input_filename = args.get<std::string>(1, false);
if (input_filename.empty() || (input_filename == "-")) { if (input_filename.empty() || (input_filename == "-")) {
throw std::runtime_error("input filename must be given"); throw std::runtime_error("input filename must be given");
} }
auto rs = load_rare_item_set( 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) { if (rate_factor != 1.0) {
rs->multiply_all_rates(rate_factor); 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 = args.get<std::string>(2, false);
std::string output_filename_lower = phosg::tolower(output_filename); std::string output_filename_lower = phosg::tolower(output_filename);
if (output_filename.empty() || (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")) { } 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); 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); write_output_data(args, data, nullptr);
} else if (output_filename_lower.ends_with(".gsl")) { } 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))) { if ((is_v1 && (difficulty == Difficulty::ULTIMATE)) || (!rs->has_entries_for_game_config(mode, episode, difficulty))) {
continue; continue;
} }
auto item_name_index = s->item_name_index(cli_version); auto item_name_index = di->item_name_index(cli_version);
std::string data = rs->serialize_html(mode, episode, difficulty, item_name_index, s->common_item_set(cli_version, nullptr)); 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); 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::save_file(out_filename, data);
phosg::log_info_f("... {}", out_filename); 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"); throw std::runtime_error("two input filenames must be given");
} }
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_text_index(); di->load_text_index();
s->load_item_definitions(); di->load_item_definitions();
s->load_item_name_indexes(); di->load_item_name_indexes();
s->load_drop_tables(); di->load_drop_tables();
bool is_v1 = ::is_v1(get_cli_version(args, Version::BB_V4)); 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 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, s->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); rs1->print_diff(stdout, *rs2);
}); });
@@ -2622,13 +2622,13 @@ Action a_describe_item(
std::string description = args.get<std::string>(1); std::string description = args.get<std::string>(1);
auto version = get_cli_version(args); auto version = get_cli_version(args);
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_text_index(); di->load_text_index();
s->load_item_definitions(); di->load_item_definitions();
s->load_item_name_indexes(); di->load_item_name_indexes();
auto name_index = s->item_name_index(version); auto name_index = di->item_name_index(version);
ItemData item = name_index->parse_item_description(description); 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]); item.data2[0], item.data2[1], item.data2[2], item.data2[3]);
ItemData item_v2 = item; 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; ItemData item_v2_decoded = item_v2;
item_v2_decoded.decode_for_version(Version::PC_V2); item_v2_decoded.decode_for_version(Version::PC_V2);
@@ -2665,7 +2665,7 @@ Action a_describe_item(
} }
ItemData item_gc = 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; ItemData item_gc_decoded = item_gc;
item_gc_decoded.decode_for_version(Version::GC_V3); 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: {}", desc);
phosg::log_info_f("Description (in-game): {}", desc_colored); 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; size_t sale_price = purchase_price >> 3;
phosg::log_info_f("Purchase price: {}; sale price: {}", purchase_price, sale_price); phosg::log_info_f("Purchase price: {}; sale price: {}", purchase_price, sale_price);
}); });
Action a_name_all_items( Action a_name_all_items(
"name-all-items", nullptr, +[](phosg::Arguments& args) { "name-all-items", nullptr, +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_text_index(); di->load_text_index();
s->load_item_definitions(); di->load_item_definitions();
s->load_item_name_indexes(); di->load_item_name_indexes();
s->load_ep3_cards(); di->load_ep3_cards();
s->load_config_late(); di->load_config_late();
std::set<uint32_t> all_primary_identifiers; 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) { if (index) {
for (const auto& it : index->all_by_primary_identifier()) { for (const auto& it : index->all_by_primary_identifier()) {
all_primary_identifiers.emplace(it.first); all_primary_identifiers.emplace(it.first);
@@ -2715,10 +2715,10 @@ Action a_name_all_items(
for (uint32_t primary_identifier : all_primary_identifiers) { for (uint32_t primary_identifier : all_primary_identifiers) {
phosg::fwrite_fmt(stdout, "{:08X}\n", primary_identifier); phosg::fwrite_fmt(stdout, "{:08X}\n", primary_identifier);
for (Version v : ALL_VERSIONS) { 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) { if (index) {
auto pmt = s->item_parameter_table(v); auto pmt = di->item_parameter_table(v);
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier); ItemData item = ItemData::from_primary_identifier(*di->item_stack_limits(v), primary_identifier);
std::string name = index->describe_item(item); std::string name = index->describe_item(item);
try { try {
bool is_rare = pmt->is_item_rare(item); bool is_rare = pmt->is_item_rare(item);
@@ -2736,7 +2736,7 @@ Action a_name_all_items(
auto print_header = [&]() -> void { auto print_header = [&]() -> void {
phosg::fwrite_fmt(stdout, "IDENT :"); phosg::fwrite_fmt(stdout, "IDENT :");
for (Version v : ALL_VERSIONS) { 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) { if (index) {
phosg::fwrite_fmt(stdout, " {:30} ", phosg::name_for_enum(v)); 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); phosg::fwrite_fmt(stdout, "{:08X}:", primary_identifier);
for (Version v : ALL_VERSIONS) { 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) { if (index) {
auto pmt = s->item_parameter_table(v); auto pmt = di->item_parameter_table(v);
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier); ItemData item = ItemData::from_primary_identifier(*di->item_stack_limits(v), primary_identifier);
if (index->exists(item)) { if (index->exists(item)) {
std::string name = index->describe_item(item); std::string name = index->describe_item(item);
bool is_rare = pmt->is_item_rare(item); bool is_rare = pmt->is_item_rare(item);
@@ -2778,10 +2778,10 @@ Action a_print_level_stats(
show-level-tables\n\ show-level-tables\n\
Print the level tables for each version in a semi-human-readable format.\n", Print the level tables for each version in a semi-human-readable format.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_level_tables(); di->load_level_tables();
std::vector<PlayerStats> level_1_v1_v2; std::vector<PlayerStats> level_1_v1_v2;
std::vector<PlayerStats> level_100_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; std::vector<PlayerStats> level_200_limit_v4;
for (size_t z = 0; z < 12; z++) { for (size_t z = 0; z < 12; z++) {
if (z < 9) { if (z < 9) {
level_1_v1_v2.emplace_back().char_stats = s->level_table_v1_v2->base_stats_for_class(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(s->level_table_v1_v2->max_stats_for_class(z)); level_200_limit_v1_v2.emplace_back(di->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); di->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); 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); level_1_v3.emplace_back().char_stats = di->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); di->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_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); level_1_v4.emplace_back().char_stats = di->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); di->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_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 { 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\ Print the item parameter tables for each version in a semi-human-readable\n\
format.\n", format.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_all(false); di->load_all();
for (Version v : ALL_VERSIONS) { 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) { if (index) {
phosg::fwrite_fmt(stdout, "======== {}\n", phosg::name_for_enum(v)); phosg::fwrite_fmt(stdout, "======== {}\n", phosg::name_for_enum(v));
index->print_table(stdout); 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\ Print the tekker and shop generation tables in a semi-human-readable\n\
format.\n", format.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_all(false); di->load_all();
s->tekker_adjustment_set->print(stdout); di->tekker_adjustment_set->print(stdout);
s->armor_random_set->print(stdout); di->armor_random_set->print(stdout);
s->tool_random_set->print(stdout); di->tool_random_set->print(stdout);
phosg::fwrite_fmt(stdout, "(Normal) "); 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) "); 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) "); 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) "); 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( Action a_show_ep3_cards(
@@ -2905,8 +2905,8 @@ Action a_show_ep3_cards(
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
bool one_line = args.get<bool>("one-line"); bool one_line = args.get<bool>("one-line");
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_ep3_cards(); di->load_ep3_cards();
std::unique_ptr<BinaryTextSet> text_english; std::unique_ptr<BinaryTextSet> text_english;
try { try {
@@ -2915,10 +2915,10 @@ Action a_show_ep3_cards(
} catch (const std::exception& e) { } 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()); phosg::log_info_f("{} card definitions", card_ids.size());
for (uint32_t card_id : card_ids) { 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())); phosg::fwrite_fmt(stdout, "{}\n", entry->def.str(one_line, text_english.get()));
if (!one_line) { if (!one_line) {
if (!entry->debug_tags.empty()) { 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_large_images = args.get<bool>("no-large-images");
bool no_disassembly = args.get<bool>("no-disassembly"); bool no_disassembly = args.get<bool>("no-disassembly");
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_patch_indexes(); di->load_patch_indexes();
s->load_text_index(); di->load_text_index();
s->load_ep3_cards(); di->load_ep3_cards();
std::shared_ptr<const TextSet> text_english; std::shared_ptr<const TextSet> text_english;
try { 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&) { } catch (const std::out_of_range&) {
} }
@@ -3063,10 +3063,10 @@ Action a_generate_ep3_cards_html(
std::vector<VersionInfo> version_infos; std::vector<VersionInfo> version_infos;
if (include_nte) { 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) { 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; std::deque<std::string> blocks;
@@ -3199,11 +3199,11 @@ Action a_show_ep3_maps(
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
config_log.info_f("Collecting Episode 3 data"); config_log.info_f("Collecting Episode 3 data");
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_ep3_cards(); di->load_ep3_cards();
s->load_ep3_maps(); 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()); phosg::log_info_f("{} maps", all_maps.size());
for (const auto& [map_number, map] : all_maps) { for (const auto& [map_number, map] : all_maps) {
const auto& vms = map->all_versions(); const auto& vms = map->all_versions();
@@ -3212,7 +3212,7 @@ Action a_show_ep3_maps(
continue; continue;
} }
Language language = static_cast<Language>(lang_index); 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); 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\ Print the Blue Burst battle parameters from the system/blueburst directory\n\
in a human-readable format.\n", in a human-readable format.\n",
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_patch_indexes(); di->load_patch_indexes();
s->load_battle_params(); di->load_battle_params();
phosg::fwrite_fmt(stdout, "Episode 1 multi\n"); 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"); 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"); 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"); 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"); 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"); 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( Action a_check_supermaps(
@@ -3253,11 +3253,11 @@ Action a_check_supermaps(
bool save_disassembly = args.get<bool>("disassemble"); bool save_disassembly = args.get<bool>("disassemble");
bool generate_enemy_stats = args.get<bool>("generate-enemy-stats"); bool generate_enemy_stats = args.get<bool>("generate-enemy-stats");
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_set_data_tables(); di->load_set_data_tables();
s->load_maps(); di->load_maps();
auto rand_crypt = std::make_shared<MT19937Generator>(phosg::random_object<uint32_t>()); 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_mode(mode),
abbreviation_for_difficulty(difficulty)); 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 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>( auto map_state = std::make_shared<MapState>(
0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps); 0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps);
map_state->verify(); map_state->verify();
@@ -3288,7 +3288,7 @@ Action a_check_supermaps(
} }
SuperMap::EfficiencyStats all_free_maps_eff; 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 episode = static_cast<Episode>((key >> 28) & 7);
auto mode = static_cast<GameMode>((key >> 26) & 3); auto mode = static_cast<GameMode>((key >> 26) & 3);
Difficulty difficulty = static_cast<Difficulty>((key >> 24) & 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()); 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; SuperMap::EfficiencyStats all_quests_eff;
uint32_t random_seed = args.get<uint32_t>("random-seed", 0, phosg::Arguments::IntFormat::HEX); 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); auto supermap = it.second->get_supermap(random_seed);
if (!supermap) { if (!supermap) {
throw std::logic_error("quest does not have a supermap, even with a specified random seed"); 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)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_set_data_tables(); di->load_set_data_tables();
s->load_maps(); di->load_maps();
std::shared_ptr<RandomGenerator> rand_crypt; std::shared_ptr<RandomGenerator> rand_crypt;
if (args.get<bool>("--psov2")) { if (args.get<bool>("--psov2")) {
@@ -3660,8 +3660,8 @@ Action a_print_free_supermap(
} else { } else {
rand_crypt = std::make_shared<PSOV2Encryption>(random_seed); 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 sdt = di->set_data_table(get_cli_version(args, Version::BB_V4), episode, mode, difficulty);
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations); 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); MapState map_state(0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps);
map_state.verify(); map_state.verify();
map_state.print(stdout); map_state.print(stdout);
@@ -3677,13 +3677,13 @@ Action a_check_quests(
check_quest_opcode_definitions(); check_quest_opcode_definitions();
phosg::log_info_f("Opcode definitions OK"); phosg::log_info_f("Opcode definitions OK");
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->is_debug = true; di->is_debug = true;
s->load_config_early(); di->load_config_early();
s->load_patch_indexes(); di->load_patch_indexes();
s->load_set_data_tables(); di->load_set_data_tables();
s->load_maps(); di->load_maps();
s->load_quest_index(true); di->load_quest_index(true);
uint64_t script_time = 0, map_time = 0; uint64_t script_time = 0, map_time = 0;
if (reassemble_scripts || reassemble_maps) { if (reassemble_scripts || reassemble_maps) {
@@ -3797,7 +3797,7 @@ Action a_check_quests(
}; };
if (num_threads == 1) { 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) { for (const auto& [_, vq] : q->versions) {
check_vq(vq, 0); check_vq(vq, 0);
} }
@@ -3805,7 +3805,7 @@ Action a_check_quests(
} else { } else {
std::vector<std::shared_ptr<const VersionedQuest>> all_vqs; 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) { for (const auto& [_, vq] : q->versions) {
all_vqs.emplace_back(vq); all_vqs.emplace_back(vq);
} }
@@ -3832,9 +3832,9 @@ Action a_check_ep3_maps(
"check-ep3-maps", nullptr, "check-ep3-maps", nullptr,
+[](phosg::Arguments& args) { +[](phosg::Arguments& args) {
config_log.info_f("Collecting Episode 3 data"); config_log.info_f("Collecting Episode 3 data");
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->is_debug = true; di->is_debug = true;
s->load_ep3_maps(true); di->load_ep3_maps(true);
}); });
Action a_check_client_functions( Action a_check_client_functions(
@@ -4029,9 +4029,9 @@ Action a_format_ep3_battle_record(
Action a_replay_ep3_battle_commands( Action a_replay_ep3_battle_commands(
"replay-ep3-battle-commands", nullptr, +[](phosg::Arguments& args) { "replay-ep3-battle-commands", nullptr, +[](phosg::Arguments& args) {
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_ep3_cards(); di->load_ep3_cards();
s->load_ep3_maps(); di->load_ep3_maps();
int64_t base_seed = args.get<int64_t>("seed", -1); int64_t base_seed = args.get<int64_t>("seed", -1);
bool is_trial = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE); 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) { auto run_replay = [&](int64_t seed, size_t) {
Episode3::Server::Options options = { Episode3::Server::Options options = {
.card_index = s->ep3_card_index, .card_index = di->ep3_card_index,
.map_index = s->ep3_map_index, .map_index = di->ep3_map_index,
.behavior_flags = 0x0092, .behavior_flags = 0x0092,
.opt_rand_stream = nullptr, .opt_rand_stream = nullptr,
.rand_crypt = std::make_shared<MT19937Generator>(seed), .rand_crypt = std::make_shared<MT19937Generator>(seed),
@@ -4088,15 +4088,15 @@ Action a_replay_ep3_battle_record(
bool use_color = isatty(fileno(stdout)); bool use_color = isatty(fileno(stdout));
auto s = std::make_shared<ServerState>(get_config_filename(args)); auto di = std::make_shared<DataIndex>(get_config_filename(args));
s->load_ep3_cards(); di->load_ep3_cards();
s->load_ep3_maps(); di->load_ep3_maps();
bool is_nte = rec->get_behavior_flags() & Episode3::BehaviorFlag::IS_TRIAL_EDITION; bool is_nte = rec->get_behavior_flags() & Episode3::BehaviorFlag::IS_TRIAL_EDITION;
auto output_queue = std::make_shared<std::deque<std::string>>(); auto output_queue = std::make_shared<std::deque<std::string>>();
Episode3::Server::Options options = { Episode3::Server::Options options = {
.card_index = s->ep3_card_index, .card_index = di->ep3_card_index,
.map_index = s->ep3_map_index, .map_index = di->ep3_map_index,
.behavior_flags = rec->get_behavior_flags() & ~(Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING), .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()), .opt_rand_stream = std::make_shared<phosg::StringReader>(rec->get_random_stream()),
.rand_crypt = std::make_shared<DisabledRandomGenerator>(), .rand_crypt = std::make_shared<DisabledRandomGenerator>(),
@@ -4220,7 +4220,20 @@ Action a_run_server_replay_log(
std::filesystem::create_directories("system/players"); 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 #ifndef PHOSG_WINDOWS
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
@@ -4229,123 +4242,157 @@ Action a_run_server_replay_log(
use_terminal_colors = true; 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")) { if (args.get<bool>("debug")) {
state->is_debug = true; data_index->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->load_all();
auto state = ServerState::create_shared(data_index, !replay_log_filenames.empty());
std::shared_ptr<ServerShell> shell; std::shared_ptr<ServerShell> shell;
std::shared_ptr<SignalWatcher> signal_watcher; 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()) { 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); state->game_server = std::make_shared<GameServer>(state);
// TODO: Do this properly via a config option, you lazy bum if (args.get<bool>("parallel")) {
state->dol_file_index = std::make_shared<DOLFileIndex>(); 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> { phosg::log_info_f("[Replay] Loading {}", log_filename);
try { auto log_f = phosg::fopen_unique(log_filename, "rt");
for (const auto& log_filename : replay_log_filenames) { auto replay_session = std::make_shared<ReplaySession>(replay_state, log_f.get());
phosg::log_info_f("[Replay] {} ...", log_filename); replay_sessions.emplace(log_filename, replay_session);
auto log_f = phosg::fopen_shared(log_filename, "rt");
last_running_replay = std::make_shared<ReplaySession>(state, log_f.get()); phosg::log_info_f("[Replay] {} ...", log_filename);
co_await last_running_replay->run(); co_await replay_session->run();
if (last_running_replay->failed()) { if (!replay_session->failure_str().empty()) {
phosg::log_error_f("[Replay] {} failed", log_filename); phosg::log_error_f("[Replay] {} failed:\n{}", log_filename, replay_session->failure_str());
break; } else {
}
phosg::log_info_f("[Replay] {} OK", log_filename); 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) { completed_count++;
phosg::log_info_f("[Replay] Replays failed: {}", e.what()); 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();
}; auto run_replays = [&]() -> asio::awaitable<void> {
asio::co_spawn(*state->io_context, run_replays, asio::detached); 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 { } 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"); 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()) { if (!state->game_server.get()) {
config_log.info_f("Starting game server"); config_log.info_f("Starting game server");
state->game_server = std::make_shared<GameServer>(state); state->game_server = std::make_shared<GameServer>(state);
} }
std::string spec = std::format("TG-{}-{}-{}-{}", std::string spec = std::format("TG-{}-{}-{}-{}",
pc->port, phosg::name_for_enum(pc->version), pc->name, phosg::name_for_enum(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); 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"); config_log.info_f("Starting IP/PPP stack simulator");
state->ip_stack_simulator = std::make_shared<IPStackSimulator>(state); 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); auto netloc = phosg::parse_netloc(it);
std::string spec = (netloc.second == 0) ? ("T-IPS-" + netloc.first) : std::format("T-IPS-{}", netloc.second); std::string spec = (netloc.second == 0) ? ("T-IPS-" + netloc.first) : std::format("T-IPS-{}", netloc.second);
state->ip_stack_simulator->listen( state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, VirtualNetworkProtocol::ETHERNET_TAPSERVER); 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); auto netloc = phosg::parse_netloc(it);
std::string spec = (netloc.second == 0) ? ("T-PPPST-" + netloc.first) : std::format("T-PPPST-{}", netloc.second); std::string spec = (netloc.second == 0) ? ("T-PPPST-" + netloc.first) : std::format("T-PPPST-{}", netloc.second);
state->ip_stack_simulator->listen( state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_TAPSERVER); 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); auto netloc = phosg::parse_netloc(it);
std::string spec = (netloc.second == 0) ? ("T-PPPSR-" + netloc.first) : std::format("T-PPPSR-{}", netloc.second); std::string spec = (netloc.second == 0) ? ("T-PPPSR-" + netloc.first) : std::format("T-PPPSR-{}", netloc.second);
state->ip_stack_simulator->listen( state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_RAW); spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_RAW);
if (netloc.second) { 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( config_log.info_f(
"Cannot generate Devolution phone numbers for {} because LocalAddress and ExternalAddress are not specified in the configuration", "Cannot generate Devolution phone numbers for {} because LocalAddress and ExternalAddress are not specified in the configuration",
spec); spec);
} else if (state->local_address == 0) { } else if (state->data->local_address == 0) {
config_log.info_f( config_log.info_f(
"Note: The Devolution phone number for {} is {} (external)", "Note: The Devolution phone number for {} is {} (external)",
spec, devolution_phone_number_for_netloc(state->external_address, netloc.second)); spec, devolution_phone_number_for_netloc(state->data->external_address, netloc.second));
} else if (state->external_address == 0) { } else if (state->data->external_address == 0) {
config_log.info_f( config_log.info_f(
"Note: The Devolution phone number for {} is {} (local)", "Note: The Devolution phone number for {} is {} (local)",
spec, devolution_phone_number_for_netloc(state->local_address, netloc.second)); spec, devolution_phone_number_for_netloc(state->data->local_address, netloc.second));
} else if (state->local_address == state->external_address) { } else if (state->data->local_address == state->data->external_address) {
config_log.info_f( config_log.info_f(
"Note: The Devolution phone number for {} is {} (local+external)", "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 { } else {
config_log.info_f( config_log.info_f(
"Note: The Devolution phone numbers for {} are {} (local) and {} (external)", "Note: The Devolution phone numbers for {} are {} (local) and {} (external)",
spec, spec,
devolution_phone_number_for_netloc(state->local_address, netloc.second), devolution_phone_number_for_netloc(state->data->local_address, netloc.second),
devolution_phone_number_for_netloc(state->external_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"); config_log.info_f("Starting HTTP server");
state->http_server = std::make_shared<HTTPServer>(state); 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); auto netloc = phosg::parse_netloc(it);
state->http_server->listen(netloc.first, netloc.second); state->http_server->listen(netloc.first, netloc.second);
} }
@@ -4358,16 +4405,16 @@ Action a_run_server_replay_log(
} }
#ifndef PHOSG_WINDOWS #ifndef PHOSG_WINDOWS
if (!state->username.empty()) { if (!state->data->username.empty()) {
config_log.info_f("Switching to user {}", state->username); config_log.info_f("Switching to user {}", state->data->username);
drop_privileges(state->username); drop_privileges(state->data->username);
} }
#endif #endif
bool should_run_shell; 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)); 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; should_run_shell = true;
} else { } else {
should_run_shell = false; should_run_shell = false;
@@ -4384,8 +4431,19 @@ Action a_run_server_replay_log(
state->io_context->run(); state->io_context->run();
config_log.info_f("Normal shutdown"); config_log.info_f("Normal shutdown");
if (last_running_replay) { if (!replay_sessions.empty()) {
throw std::runtime_error("Replay failed"); 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"); c->log.info_f("No item was created");
} else { } else {
auto s = c->require_server_state(); 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); 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++; res.item.id = c->proxy_session->next_item_id++;
c->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for all clients", 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(); auto s = c->require_server_state();
c->proxy_session->set_drop_mode(s, c->version(), c->override_random_seed, c->proxy_session->drop_mode); 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)) { 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_episode,
c->proxy_session->lobby_mode, c->proxy_session->lobby_mode,
c->proxy_session->lobby_difficulty, 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(); auto s = c->require_server_state();
char command_sentinel = s->chat_command_sentinel char command_sentinel = s->data->chat_command_sentinel
? s->chat_command_sentinel ? s->data->chat_command_sentinel
: ((c->version() == Version::DC_11_2000) ? '@' : '$'); : ((c->version() == Version::DC_11_2000) ? '@' : '$');
bool is_command = (text[0] == command_sentinel) || bool is_command = (text[0] == command_sentinel) ||
(text[0] == '\t' && text[1] != 'C' && text[2] == 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) { if (this->drop_mode == ProxyDropMode::INTERCEPT) {
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed); 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>( this->item_creator = std::make_shared<ItemCreator>(
s->common_item_set(version, nullptr), s->data->common_item_set(version, nullptr),
s->rare_item_set(version, nullptr), s->data->rare_item_set(version, nullptr),
s->armor_random_set, s->data->armor_random_set,
s->tool_random_set, s->data->tool_random_set,
s->weapon_random_set(this->lobby_difficulty), s->data->weapon_random_set(this->lobby_difficulty),
s->tekker_adjustment_set, s->data->tekker_adjustment_set,
s->item_parameter_table(version), s->data->item_parameter_table(version),
s->item_stack_limits(version), s->data->item_stack_limits(version),
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode, (this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
this->lobby_difficulty, this->lobby_difficulty,
this->lobby_section_id, this->lobby_section_id,
+1 -1
View File
@@ -13,7 +13,7 @@
#include "Map.hh" #include "Map.hh"
#include "SaveFileFormats.hh" #include "SaveFileFormats.hh"
struct ServerState; class ServerState;
struct ProxySession { struct ProxySession {
static size_t num_proxy_sessions; 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) { asio::awaitable<void> on_connect(std::shared_ptr<Client> c) {
auto s = c->require_server_state(); 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); c->set_flag(Client::Flag::SWITCH_ASSIST_ENABLED);
} }
switch (c->server_behavior) { switch (c->server_behavior) {
case ServerBehavior::PC_CONSOLE_DETECT: { case ServerBehavior::PC_CONSOLE_DETECT: {
uint16_t pc_port = s->name_to_port_config.at("pc")->port; uint16_t pc_port = s->data->name_to_port_config.at("pc").port;
uint16_t console_port = s->name_to_port_config.at("gc-us3")->port; uint16_t console_port = s->data->name_to_port_config.at("gc-us3").port;
send_pc_console_split_reconnect(c, s->connect_address_for_client(c), pc_port, console_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 // 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 // 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 // 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) { static void send_main_menu(std::shared_ptr<Client> c) {
auto s = c->require_server_state(); 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( main_menu->items.emplace_back(
MainMenuItemID::GO_TO_LOBBY, "Go to lobby", MainMenuItemID::GO_TO_LOBBY, "Go to lobby",
[wc = std::weak_ptr<Client>(c)]() -> std::string { [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); "View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DC_PROTOS | MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
uint32_t proxy_destinations_menu_item_flags = uint32_t proxy_destinations_menu_item_flags =
(s->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) | (s->data->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
(s->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) | (s->data->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
(s->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) | (s->data->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_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) |
MenuItem::Flag::INVISIBLE_ON_BB; MenuItem::Flag::INVISIBLE_ON_BB;
main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, "Proxy server", main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, "Proxy server",
"Connect to another\nserver through the\nproxy", proxy_destinations_menu_item_flags); "Connect to another\nserver through the\nproxy", proxy_destinations_menu_item_flags);
main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, "Download quests", 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); "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", main_menu->items.emplace_back(MainMenuItemID::PATCH_SWITCHES, "Patches",
"Change game\nbehaviors", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE); "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", 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); "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) { static void send_proxy_destinations_menu(std::shared_ptr<Client> c) {
auto s = c->require_server_state(); 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) { 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, 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"); "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)) { c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) {
if (!is_ep3(c->version())) { if (!is_ep3(c->version())) {
add_flag_option(ProxyOptionsMenuItemID::INFINITE_HP, Client::Flag::INFINITE_HP_ENABLED, 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"); "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, 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.)"); "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(); auto s = c->require_server_state();
if (c->login->account->auto_patches_enabled.empty() && if (c->login->account->auto_patches_enabled.empty() &&
((c->version() != Version::BB_V4) || s->bb_required_patches.empty()) && ((c->version() != Version::BB_V4) || s->data->bb_required_patches.empty()) &&
s->auto_patches.empty()) { s->data->auto_patches.empty()) {
c->set_flag(Client::Flag::HAS_AUTO_PATCHES); 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; std::unordered_set<std::shared_ptr<const ClientFunctionIndex::Function>> functions_to_send;
if (c->version() == Version::BB_V4) { 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 { 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&) { } catch (const std::out_of_range&) {
std::string message = std::format( 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); "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 { 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&) { } catch (const std::out_of_range&) {
c->log.warning_f("Server has auto patch {} enabled, but it is not available for specific_version {}", 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)); 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) { for (const auto& patch_name : c->login->account->auto_patches_enabled) {
try { 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&) { } catch (const std::out_of_range&) {
c->log.warning_f("Client has auto patch {} enabled, but it is not available for specific_version {}", 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)); 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); s->remove_client_from_lobby(c);
} }
if (s->pre_lobby_event && (!is_ep3(c->version()) || s->ep3_menu_song < 0)) { if (s->data->pre_lobby_event && (!is_ep3(c->version()) || s->data->ep3_menu_song < 0)) {
send_change_event(c, s->pre_lobby_event); send_change_event(c, s->data->pre_lobby_event);
} }
send_server_time(c); 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); 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)) { 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); send_ep3_media_update(c, banner.type, banner.which, banner.data);
c->set_flag(Client::Flag::HAS_EP3_MEDIA_UPDATES); 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->set_flag(Client::Flag::LOADING);
c->log.info_f("LOADING flag set"); 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::NO_D6) ||
!c->check_flag(Client::Flag::AT_WELCOME_MESSAGE)) { !c->check_flag(Client::Flag::AT_WELCOME_MESSAGE)) {
c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE); c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE);
send_main_menu(c); send_main_menu(c);
} else { } else {
send_message_box(c, s->welcome_message); send_message_box(c, s->data->welcome_message);
} }
co_return; 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))) { !c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE))) {
std::shared_ptr<const Quest> q; std::shared_ptr<const Quest> q;
try { 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&) { } catch (const std::out_of_range&) {
} }
if (!q) { 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()) { if (!c->can_use_chat_commands()) {
c->clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); 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); 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_YELLOW,
phosg::TerminalFormat::FG_RED, phosg::TerminalFormat::FG_RED,
false, false,
s->censor_credentials); s->data->censor_credentials);
c->proxy_session = std::make_shared<ProxySession>(channel, pc); c->proxy_session = std::make_shared<ProxySession>(channel, pc);
if (c->version() == Version::GC_EP3) { 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) { 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); send_ship_info(c, msg);
c->channel->disconnect(); c->channel->disconnect();
} else { } 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); send_ship_info(c, msg);
co_await start_login_server_procedure(c); 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) { } catch (const AccountIndex::incorrect_password& e) {
result_code = 0x03; result_code = 0x03;
} catch (const AccountIndex::missing_account& e) { } catch (const AccountIndex::missing_account& e) {
if (!s->allow_unregistered_users) { if (!s->data->allow_unregistered_users) {
result_code = 0x08; result_code = 0x08;
} }
} }
} else if (!c->username.empty() && !s->allow_unregistered_users) { } else if (!c->username.empty() && !s->data->allow_unregistered_users) {
try { try {
s->account_index->from_bb_credentials(c->username, nullptr, false); s->account_index->from_bb_credentials(c->username, nullptr, false);
} catch (const AccountIndex::missing_account& e) { } 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 // Switch to proxy session if there's a destination configured
if (is_patch(c->version()) && s->proxy_destination_patch.has_value()) { if (is_patch(c->version()) && s->data->proxy_destination_patch.has_value()) {
const auto& [host, port] = *s->proxy_destination_patch; const auto& [host, port] = *s->data->proxy_destination_patch;
co_await start_proxy_session(c, host, port, false); co_await start_proxy_session(c, host, port, false);
} else { } else {
// No proxy destination; continue with normal patch logic // No proxy destination; continue with normal patch logic
bool is_bb = (c->version() == Version::BB_PATCH); 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()) { if (!message.empty()) {
send_message_box(c, message); 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()) { if (index.get()) {
c->channel->send(0x0B, 0x00); // Start patch session; go to root directory 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 { try {
auto s = c->require_server_state(); auto s = c->require_server_state();
c->set_login(s->account_index->from_gc_credentials( 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); send_command(c, 0x9A, 0x02);
} catch (const AccountIndex::no_username& e) { } 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 { try {
auto s = c->require_server_state(); 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); send_command(c, 0x88, 0x00);
} catch (const AccountIndex::no_username& e) { } 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 { try {
auto s = c->require_server_state(); 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) { } catch (const AccountIndex::no_username& e) {
c->log.info_f("Login failed (no username)"); c->log.info_f("Login failed (no username)");
send_message_box(c, "Incorrect serial number"); 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 { try {
auto s = c->require_server_state(); auto s = c->require_server_state();
if (c->serial_number.size() > 8 || c->access_key.size() > 8) { 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 { } else {
serial_number = stoull(c->serial_number, nullptr, 16); 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)) { if (c->log.should_log(phosg::LogLevel::L_INFO)) {
c->log.info_f("Received login: {}", c->login->str()); 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; uint32_t serial_number = 0;
try { try {
if (c->serial_number.size() > 8 || c->access_key.size() > 8) { 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 { } else {
serial_number = stoull(c->serial_number, nullptr, 16); serial_number = stoull(c->serial_number, nullptr, 16);
c->set_login(s->account_index->from_dc_credentials( 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)) { if (c->log.should_log(phosg::LogLevel::L_INFO)) {
c->log.info_f("Login: {}", c->login->str()); 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()) { switch (c->version()) {
case Version::DC_V2: { case Version::DC_V2: {
uint32_t serial_number = stoul(c->serial_number, nullptr, 16); 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)) { if (c->log.should_log(phosg::LogLevel::L_INFO)) {
c->log.info_f("Login: {}", c->login->str()); 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->channel->version = Version::PC_NTE;
c->log.info_f("Changed client version to PC_NTE"); c->log.info_f("Changed client version to PC_NTE");
c->set_login(s->account_index->from_pc_nte_credentials( 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 { } else {
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); 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; break;
} }
@@ -1253,7 +1253,7 @@ static asio::awaitable<void> on_9D_9E(std::shared_ptr<Client> c, Channel::Messag
case Version::DC_V2: { case Version::DC_V2: {
uint32_t serial_number = stoul(c->serial_number, nullptr, 16); uint32_t serial_number = stoul(c->serial_number, nullptr, 16);
c->set_login(s->account_index->from_dc_credentials( 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; break;
} }
case Version::PC_NTE: 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->channel->version = Version::PC_NTE;
c->log.info_f("Changed client version to PC_NTE"); c->log.info_f("Changed client version to PC_NTE");
c->set_login(s->account_index->from_pc_nte_credentials( 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 { } else {
uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16);
c->set_login(s->account_index->from_pc_credentials( 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; break;
case Version::GC_NTE: 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 // not; we'll call on_login_complete once we receive the B3 response
if (c->version() == Version::PC_V2) { if (c->version() == Version::PC_V2) {
try { 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}}; 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); 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_user_id = stoull(c->access_key, nullptr, 16);
uint64_t xb_account_id = cmd.xb_netloc.account_id; uint64_t xb_account_id = cmd.xb_netloc.account_id;
try { 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) { } catch (const AccountIndex::no_username& e) {
c->log.info_f("Login failed (no username)"); c->log.info_f("Login failed (no username)");
send_command(c, 0x04, 0x03); 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(); auto s = c->require_server_state();
try { 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) { } catch (const AccountIndex::no_username& e) {
c->log.info_f("Login failed (no username)"); c->log.info_f("Login failed (no username)");
send_client_init_bb(c, 0x08); 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 // 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... // didn't just make it use the initial connection address by default...
send_client_init_bb(c, 0); 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; 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 // 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 // case. This is because the login command is resent to the remote server, and we forward its response back to the
// client directly. // 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); co_await start_proxy_session(c, host, port, c->bb_connection_phase != 0);
c->proxy_session->remote_client_config_data = c->bb_client_config; c->proxy_session->remote_client_config_data = c->bb_client_config;
co_return; 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. // ship select menu or a lobby join command.
co_await on_login_complete(c); 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 // 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. // 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"); 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). // 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 s = c->require_server_state();
auto l = c->lobby.lock(); auto l = c->lobby.lock();
if (!l && (s->ep3_menu_song >= 0)) { if (!l && (s->data->ep3_menu_song >= 0)) {
send_ep3_change_music(c->channel, s->ep3_menu_song); send_ep3_change_music(c->channel, s->data->ep3_menu_song);
} }
co_return; 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(); bool is_lobby = l && !l->is_game();
uint32_t current_meseta, total_meseta_earned; uint32_t current_meseta, total_meseta_earned;
if (s->ep3_infinite_meseta) { if (s->data->ep3_infinite_meseta) {
current_meseta = 1000000; current_meseta = 1000000;
total_meseta_earned = 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; current_meseta = c->login->account->ep3_current_meseta;
total_meseta_earned = c->login->account->ep3_total_meseta_earned; total_meseta_earned = c->login->account->ep3_total_meseta_earned;
} else { } 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.first_team_turn = 0xFF;
state_cmd.state.tournament_flag = 0x01; state_cmd.state.tournament_flag = 0x01;
state_cmd.state.client_sc_card_types.clear(Episode3::CardType::INVALID_FF); 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; uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&state_cmd, sizeof(state_cmd), mask_key); 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->tournament_match = tourn_match;
game->ep3_ex_result_values = (tourn_match && tourn && tourn->get_final_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->data->ep3_tournament_final_round_ex_values
: s->ep3_tournament_ex_values; : s->data->ep3_tournament_ex_values;
game->clients_to_add.clear(); game->clients_to_add.clear();
for (const auto& it : game_clients) { for (const auto& it : game_clients) {
game->clients_to_add.emplace(it.first, it.second); 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) { if (!l->ep3_server || l->ep3_server->battle_finished) {
auto s = c->require_server_state(); auto s = c->require_server_state();
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_RECORDING) { if (s->data->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_RECORDING) {
l->battle_record = std::make_shared<Episode3::BattleRecord>(s->ep3_behavior_flags); l->battle_record = std::make_shared<Episode3::BattleRecord>(s->data->ep3_behavior_flags);
for (auto existing_c : l->clients) { for (auto existing_c : l->clients) {
if (existing_c) { if (existing_c) {
auto existing_p = existing_c->character_file(); 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) { for (const auto& rc : rl->clients) {
if (rc) { if (rc) {
rc->ep3_prev_battle_record = l->battle_record; 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"); 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; uint32_t meseta_reward = 0;
auto& round_rewards = loser_team->has_any_human_players() auto& round_rewards = loser_team->has_any_human_players()
? s->ep3_defeat_player_meseta_rewards ? s->data->ep3_defeat_player_meseta_rewards
: s->ep3_defeat_com_meseta_rewards; : s->data->ep3_defeat_com_meseta_rewards;
meseta_reward = (l->tournament_match->round_num - 1 < round_rewards.size()) meseta_reward = (l->tournament_match->round_num - 1 < round_rewards.size())
? round_rewards[l->tournament_match->round_num - 1] ? round_rewards[l->tournament_match->round_num - 1]
: round_rewards.back(); : round_rewards.back();
if (tourn && (l->tournament_match == tourn->get_final_match())) { 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) { for (const auto& player : winner_team->players) {
if (player.is_human()) { 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); check_size_v(msg.data.size(), 0);
if (c->check_flag(Client::Flag::IN_INFORMATION_MENU)) { if (c->check_flag(Client::Flag::IN_INFORMATION_MENU)) {
auto s = c->require_server_state(); 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)) { } else if (c->check_flag(Client::Flag::AT_WELCOME_MESSAGE)) {
c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE); c->clear_flag(Client::Flag::AT_WELCOME_MESSAGE);
send_main_menu(c); 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_EP1:
case MenuID::QUEST_EP2: { case MenuID::QUEST_EP2: {
bool is_download_quest = !c->lobby.lock(); 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); send_quest_info(c, "$C7Quests are not available.", 0x00, is_download_quest);
} else { } else {
auto q = s->quest_index->get(cmd.item_id); auto q = s->data->quest_index->get(cmd.item_id);
if (!q) { if (!q) {
send_quest_info(c, "$C4Quest does not\nexist.", 0x00, is_download_quest); send_quest_info(c, "$C4Quest does not\nexist.", 0x00, is_download_quest);
} else { } 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_TRIAL
: Episode3::MapIndex::VisibilityFlag::ONLINE_FINAL; : 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)) { if (!map || !map->check_visibility_flag(vis_flag)) {
send_quest_info(c, "$C4Map does not exist.", 0x00, true); send_quest_info(c, "$C4Map does not exist.", 0x00, true);
} else { } else {
@@ -2408,7 +2408,8 @@ static void on_quest_loaded(std::shared_ptr<Lobby> l) {
if (is_v4(lc->version())) { if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index); 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"); lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true); 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())) { if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index); 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"); 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: { 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); c->set_flag(Client::Flag::IN_INFORMATION_MENU);
break; 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 // 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. // need to know the client's specific_version before sending the menu.
co_await prepare_client_for_patches(c); 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; 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"); throw std::runtime_error("client does not support send_function_call");
} }
co_await prepare_client_for_patches(c); 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; break;
} }
@@ -2572,7 +2573,7 @@ static asio::awaitable<void> on_10_main_menu(std::shared_ptr<Client> c, uint32_t
break; break;
case MainMenuItemID::CLEAR_LICENSE: { 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", conf_menu->items.emplace_back(ClearLicenseConfirmationMenuItemID::CANCEL, "Go back",
"Go back to the\nmain menu", 0); "Go back to the\nmain menu", 0);
conf_menu->items.emplace_back(ClearLicenseConfirmationMenuItemID::CLEAR_LICENSE, "Clear license", 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); send_main_menu(c);
} else { } else {
try { 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)); send_message_box(c, contents->at(item_id));
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
send_message_box(c, "$C6No such information exists."); 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(); auto s = c->require_server_state();
const std::pair<std::string, uint16_t>* dest = nullptr; const std::pair<std::string, uint16_t>* dest = nullptr;
try { 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&) { } 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) { static void on_10_quest_categories(std::shared_ptr<Client> c, uint32_t item_id) {
if (is_ep3(c->version())) { if (is_ep3(c->version())) {
auto s = c->require_server_state(); 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."); send_lobby_message_box(c, "$C7Quests are not available.");
return; return;
} }
@@ -2792,7 +2793,7 @@ static void on_10_quest_categories(std::shared_ptr<Client> c, uint32_t item_id)
} else { } else {
auto s = c->require_server_state(); auto s = c->require_server_state();
if (!s->quest_index) { if (!s->data->quest_index) {
send_lobby_message_box(c, "$C7Quests are not available."); send_lobby_message_box(c, "$C7Quests are not available.");
return; 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(); 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); 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(); auto s = c->require_server_state();
if (!s->quest_index) { if (!s->data->quest_index) {
send_lobby_message_box(c, "$C7Quests are not\navailable."); send_lobby_message_box(c, "$C7Quests are not\navailable.");
return; return;
} }
auto q = s->quest_index->get(item_id); auto q = s->data->quest_index->get(item_id);
if (!q) { if (!q) {
send_lobby_message_box(c, "$C7Quest does not exist."); send_lobby_message_box(c, "$C7Quest does not exist.");
return; 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"); 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) auto vis_flag = (c->version() == Version::GC_EP3_NTE)
? Episode3::MapIndex::VisibilityFlag::ONLINE_TRIAL ? 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 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) { 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->auto_patches_enabled.erase(fn->short_name);
} }
c->login->account->save(); 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 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 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) { static asio::awaitable<void> on_1F(std::shared_ptr<Client> c, Channel::Message& msg) {
check_size_v(msg.data.size(), 0); check_size_v(msg.data.size(), 0);
auto s = c->require_server_state(); 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; 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(); auto s = c->require_server_state();
char command_sentinel = s->chat_command_sentinel char command_sentinel = s->data->chat_command_sentinel
? s->chat_command_sentinel ? s->data->chat_command_sentinel
: ((c->version() == Version::DC_11_2000) ? '@' : '$'); : ((c->version() == Version::DC_11_2000) ? '@' : '$');
if ((text[0] == command_sentinel) && c->can_use_chat_commands()) { if ((text[0] == command_sentinel) && c->can_use_chat_commands()) {
if (text[1] == command_sentinel) { if (text[1] == command_sentinel) {
@@ -3970,7 +3971,7 @@ static asio::awaitable<void> on_E5_BB(std::shared_ptr<Client> c, Channel::Messag
try { try {
auto s = c->require_server_state(); auto s = c->require_server_state();
c->create_character_file( 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) { } catch (const std::exception& e) {
send_message_box(c, std::format("$C6New character could not be created:\n{}", e.what())); send_message_box(c, std::format("$C6New character could not be created:\n{}", e.what()));
should_send_approve = false; 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())) { if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index); 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"); lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true); 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.ep2_online_award_state
: p->challenge_records.ep1_online_award_state; : p->challenge_records.ep1_online_award_state;
award_state.rank_award_flags |= cmd.rank_bitmask; 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->on_item_id_generated_externally(cmd.item.id);
l->log.info_f("(Challenge mode) Item awarded to player {}: {}", 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; 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()); result.info_string.encode(info_string, c->language());
std::string location_string; std::string location_string;
if (l->is_game()) { 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()) { } 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 { } 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.location_string.encode(location_string, c->language());
result.reconnect_command_header.command = 0x19; result.reconnect_command_header.command = 0x19;
result.reconnect_command_header.flag = 0x00; result.reconnect_command_header.flag = 0x00;
result.reconnect_command_header.size = sizeof(result.reconnect_command) + sizeof(result.reconnect_command_header); 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.address = s->data->connect_address_for_client(c);
result.reconnect_command.port = s->game_server_port_for_version(c->version()); 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].menu_id = MenuID::LOBBY;
result.meet_user.lobby_refs[0].item_id = l->lobby_id; 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()); 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(); 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(); auto p = creator_c->character_file();
if (!creator_c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) { 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->episode = episode;
game->mode = mode; game->mode = mode;
game->difficulty = difficulty; 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"); 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)) { if (!allow_v1 || (difficulty == Difficulty::ULTIMATE) || (mode == GameMode::CHALLENGE) || (mode == GameMode::SOLO)) {
game->forbid_version(Version::DC_NTE); 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()); 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); 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); 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); game->set_flag(Lobby::Flag::USE_CREATOR_SECTION_ID);
} }
if (watched_lobby || battle_player) { if (watched_lobby || battle_player) {
@@ -4616,8 +4617,8 @@ std::shared_ptr<Lobby> create_game_generic(
game->battle_player = battle_player; game->battle_player = battle_player;
battle_player->set_lobby(game); battle_player->set_lobby(game);
} }
game->base_exp_multiplier = s->bb_global_exp_multiplier; game->base_exp_multiplier = s->data->bb_global_exp_multiplier;
game->exp_share_multiplier = s->exp_share_multiplier; game->exp_share_multiplier = s->data->exp_share_multiplier;
const std::unordered_map<uint16_t, IntegralExpression>* quest_flag_rewrites; const std::unordered_map<uint16_t, IntegralExpression>* quest_flag_rewrites;
switch (creator_c->version()) { switch (creator_c->version()) {
@@ -4627,31 +4628,31 @@ std::shared_ptr<Lobby> create_game_generic(
case Version::DC_V2: case Version::DC_V2:
case Version::PC_NTE: case Version::PC_NTE:
case Version::PC_V2: 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) { if (game->mode == GameMode::BATTLE) {
game->drop_mode = s->default_drop_mode_v1_v2_battle; game->drop_mode = s->data->default_drop_mode_v1_v2_battle;
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_battle; game->allowed_drop_modes = s->data->allowed_drop_modes_v1_v2_battle;
} else if (game->mode == GameMode::CHALLENGE) { } else if (game->mode == GameMode::CHALLENGE) {
game->drop_mode = s->default_drop_mode_v1_v2_challenge; game->drop_mode = s->data->default_drop_mode_v1_v2_challenge;
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_challenge; game->allowed_drop_modes = s->data->allowed_drop_modes_v1_v2_challenge;
} else { } else {
game->drop_mode = s->default_drop_mode_v1_v2_normal; game->drop_mode = s->data->default_drop_mode_v1_v2_normal;
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_normal; game->allowed_drop_modes = s->data->allowed_drop_modes_v1_v2_normal;
} }
break; break;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::XB_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) { if (game->mode == GameMode::BATTLE) {
game->drop_mode = s->default_drop_mode_v3_battle; game->drop_mode = s->data->default_drop_mode_v3_battle;
game->allowed_drop_modes = s->allowed_drop_modes_v3_battle; game->allowed_drop_modes = s->data->allowed_drop_modes_v3_battle;
} else if (game->mode == GameMode::CHALLENGE) { } else if (game->mode == GameMode::CHALLENGE) {
game->drop_mode = s->default_drop_mode_v3_challenge; game->drop_mode = s->data->default_drop_mode_v3_challenge;
game->allowed_drop_modes = s->allowed_drop_modes_v3_challenge; game->allowed_drop_modes = s->data->allowed_drop_modes_v3_challenge;
} else { } else {
game->drop_mode = s->default_drop_mode_v3_normal; game->drop_mode = s->data->default_drop_mode_v3_normal;
game->allowed_drop_modes = s->allowed_drop_modes_v3_normal; game->allowed_drop_modes = s->data->allowed_drop_modes_v3_normal;
} }
break; break;
case Version::GC_EP3_NTE: 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)); game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
break; break;
case Version::BB_V4: 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) { if (game->mode == GameMode::BATTLE) {
game->drop_mode = s->default_drop_mode_v4_battle; game->drop_mode = s->data->default_drop_mode_v4_battle;
game->allowed_drop_modes = s->allowed_drop_modes_v4_battle; game->allowed_drop_modes = s->data->allowed_drop_modes_v4_battle;
} else if (game->mode == GameMode::CHALLENGE) { } else if (game->mode == GameMode::CHALLENGE) {
game->drop_mode = s->default_drop_mode_v4_challenge; game->drop_mode = s->data->default_drop_mode_v4_challenge;
game->allowed_drop_modes = s->allowed_drop_modes_v4_challenge; game->allowed_drop_modes = s->data->allowed_drop_modes_v4_challenge;
} else { } else {
game->drop_mode = s->default_drop_mode_v4_normal; game->drop_mode = s->data->default_drop_mode_v4_normal;
game->allowed_drop_modes = s->allowed_drop_modes_v4_normal; game->allowed_drop_modes = s->data->allowed_drop_modes_v4_normal;
} }
// Disallow CLIENT mode on BB // Disallow CLIENT mode on BB
if (game->drop_mode == ServerDropMode::CLIENT) { if (game->drop_mode == ServerDropMode::CLIENT) {
@@ -4698,9 +4699,9 @@ std::shared_ptr<Lobby> create_game_generic(
bool is_solo = (game->mode == GameMode::SOLO); bool is_solo = (game->mode == GameMode::SOLO);
if (game->mode == GameMode::CHALLENGE) { 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 { } 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) { if (game->episode != Episode::EP3) {
@@ -4714,7 +4715,7 @@ std::shared_ptr<Lobby> create_game_generic(
auto vars_str = game->variations.str(); auto vars_str = game->variations.str();
game->log.info_f("Using variations from client override: {}", vars_str); game->log.info_f("Using variations from client override: {}", vars_str);
} else { } 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); game->variations = sdt->generate_variations(game->episode, is_solo, game->rand_crypt);
auto vars_str = game->variations.str(); auto vars_str = game->variations.str();
game->log.info_f("Using random variations: {}", vars_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); 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)) { 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) { if (spectators_forbidden) {
game->set_flag(Lobby::Flag::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(); auto s = c->require_server_state();
std::shared_ptr<const Quest> q; std::shared_ptr<const Quest> q;
try { try {
int64_t quest_num = s->enable_send_function_call_quest_numbers.at(c->specific_version); int64_t quest_num = s->data->enable_send_function_call_quest_numbers.at(c->specific_version);
q = s->quest_index->get(quest_num); q = s->data->quest_index->get(quest_num);
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
throw std::logic_error("cannot find patch enable quest after it was previously found during login"); 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 // Delete items that are being given away
for (const auto& item : c->pending_item_trade->items) { for (const auto& item : c->pending_item_trade->items) {
size_t amount = item.stack_size(*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->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 // 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 // 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) { for (const auto& trade_item : other_c->pending_item_trade->items) {
ItemData added_item = trade_item; ItemData added_item = trade_item;
added_item.id = l->generate_item_id(c->lobby_client_id); 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_create_inventory_item_to_lobby(c, c->lobby_client_id, added_item);
} }
send_command(c, 0xD3, 0x00); 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); send_team_metadata_change_notifications(s, team, 0, TeamMetadataChange::REWARD_FLAGS);
} }
if (!reward.reward_item.empty()) { 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(); 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); out_cmd.header.subcommand = translate_subcommand_number(lc->version(), c->version(), out_cmd.header.subcommand);
if (out_cmd.header.subcommand) { if (out_cmd.header.subcommand) {
out_cmd.item_data.decode_for_version(c->version()); 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); send_command_t(lc, command, flag, out_cmd);
} else { } else {
lc->log.info_f("Subcommand cannot be translated to client\'s version"); 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.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh; ret.visual.sh = this->visual_sh;
ret.visual.enforce_lobby_join_limits_for_version(Version::DC_NTE); 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) { if (name_color) {
ret.visual.sh.name_color = name_color; ret.visual.sh.name_color = name_color;
ret.visual.sh.compute_name_color_checksum(); 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, ret.num_items,
this->item_version, this->item_version,
Version::DC_NTE, Version::DC_NTE,
s->item_parameter_table_for_encode(Version::DC_NTE)); s->data->item_parameter_table_for_encode(Version::DC_NTE));
return ret; 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.player_flags = this->get_player_flags(false);
ret.visual.name.encode(this->name, this->language); ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh; 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) { if (name_color) {
ret.visual.sh.name_color = name_color; ret.visual.sh.name_color = name_color;
ret.visual.sh.compute_name_color_checksum(); ret.visual.sh.compute_name_color_checksum();
@@ -980,7 +980,7 @@ G_SyncPlayerDispAndInventory_DC112000_6x70 Parsed6x70Data::as_dc_112000(std::sha
ret.num_items, ret.num_items,
this->item_version, this->item_version,
Version::DC_11_2000, 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); ret.visual.enforce_lobby_join_limits_for_version(Version::DC_11_2000);
return ret; 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.base = this->base_v1(false);
ret.visual.name.encode(this->name, this->language); ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh; 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) { if (name_color) {
ret.visual.sh.name_color = name_color; ret.visual.sh.name_color = name_color;
ret.visual.sh.compute_name_color_checksum(); 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.num_items = this->num_items;
ret.items = this->items; ret.items = this->items;
transcode_inventory_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); ret.visual.sh.enforce_lobby_join_limits_for_version(to_version);
return ret; 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.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh; ret.visual.sh = this->visual_sh;
ret.visual.enforce_lobby_join_limits_for_version(to_version); 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) { if (name_color) {
ret.visual.sh.name_color = name_color; ret.visual.sh.name_color = name_color;
if (is_v1_or_v2(to_version)) { 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.num_items = this->num_items;
ret.items = this->items; ret.items = this->items;
transcode_inventory_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; ret.floor = this->floor;
return ret; return ret;
} }
@@ -1033,7 +1033,7 @@ G_SyncPlayerDispAndInventory_XB_6x70 Parsed6x70Data::as_xb(std::shared_ptr<Serve
ret.base = this->base_v1(true); ret.base = this->base_v1(true);
ret.visual.name.encode(this->name, this->language); ret.visual.name.encode(this->name, this->language);
ret.visual.sh = this->visual_sh; 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) { if (name_color) {
ret.visual.sh.name_color = name_color; ret.visual.sh.name_color = name_color;
ret.visual.sh.name_color_checksum = 0; 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.num_items = this->num_items;
ret.items = this->items; ret.items = this->items;
transcode_inventory_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.visual.sh.enforce_lobby_join_limits_for_version(Version::XB_V3);
ret.floor = this->floor; ret.floor = this->floor;
ret.xb_user_id_high = this->xb_user_id >> 32; 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.sh = this->visual_sh;
ret.visual.name.encode(this->name, this->language); ret.visual.name.encode(this->name, this->language);
ret.visual.enforce_lobby_join_limits_for_version(Version::BB_V4); 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) { if (name_color) {
ret.visual.sh.name_color = name_color; ret.visual.sh.name_color = name_color;
ret.visual.sh.name_color_checksum = 0; 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.num_items = this->num_items;
ret.items = this->items; ret.items = this->items;
transcode_inventory_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.floor = this->floor;
ret.xb_user_id_high = this->xb_user_id >> 32; ret.xb_user_id_high = this->xb_user_id >> 32;
ret.xb_user_id_low = this->xb_user_id; 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)) { if (!lc || (lc == c)) {
continue; 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); 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); 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())) { if (is_big_endian(lc->version())) {
G_WordSelectBE_6x74 out_cmd = { G_WordSelectBE_6x74 out_cmd = {
subcommand, cmd.size, cmd.client_id.load(), 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); send_command_t(lc, 0x60, 0x00, out_cmd);
} else { } else {
G_WordSelect_6x74 out_cmd = { G_WordSelect_6x74 out_cmd = {
subcommand, cmd.size, cmd.client_id.load(), 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); 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 s = c->require_server_state();
auto l = c->require_lobby(); auto l = c->require_lobby();
auto p = c->character_file(); 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); l->add_item(cmd.floor, item, cmd.pos, nullptr, nullptr, 0x00F);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state(); 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})", 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); cmd.header.client_id, cmd.item_id, name, cmd.floor, cmd.pos.x, cmd.pos.z);
c->print_inventory(); 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)) { 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); 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 { } 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)) { 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); l->log.info_f("Player {} created inventory item {:08X} ({})", c->lobby_client_id, item.id, name);
c->print_inventory(); 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)) { if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state(); 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})", 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); cmd.header.client_id, item.id, name, cmd.floor, cmd.pos.x, cmd.pos.z);
c->print_inventory(); 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 s = c->require_server_state();
auto p = c->character_file(); 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); 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 // 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)) { if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state(); 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})", 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); cmd.header.client_id, cmd.item_id, name, cmd.floor, cmd.pos.x, cmd.pos.z);
c->print_inventory(); 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.data2d = 0; // Clear the price field
item.decode_for_version(c->version()); item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id); 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); p->remove_meseta(price, c->version() != Version::BB_V4);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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)", l->log.info_f("Player {} bought item {:08X} ({}) from shop ({} Meseta)",
cmd.header.client_id, item.id, name, price); cmd.header.client_id, item.id, name, price);
c->print_inventory(); c->print_inventory();
@@ -2125,7 +2126,7 @@ void send_item_notification_if_needed(std::shared_ptr<Client> c, const ItemData&
break; break;
case Client::ItemDropNotificationMode::RARES_ONLY: case Client::ItemDropNotificationMode::RARES_ONLY:
should_notify = (is_from_rare_table || (item.data1[0] == 0x03)) && 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; should_include_rare_header = true;
break; break;
case Client::ItemDropNotificationMode::ALL_ITEMS: 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) { 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" : ""); const char* rare_header = (should_include_rare_header ? "$C6Rare item dropped:\n" : "");
send_text_message_fmt(c, "{}{}", rare_header, name); 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"); 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::EnemyState> ene_st;
std::shared_ptr<const MapState::ObjectState> obj_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->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); 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->log.info_f("Player {} (leader) created floor item {:08X} ({}){} at {}:({:g}, {:g})",
l->leader_id, l->leader_id, item.id, name, from_entity_str, cmd.item.floor, cmd.item.pos.x, cmd.item.pos.z);
item.id,
name,
from_entity_str,
cmd.item.floor,
cmd.item.pos.x,
cmd.item.pos.z);
for (auto& lc : l->clients) { for (auto& lc : l->clients) {
if (!lc) { if (!lc) {
@@ -2253,7 +2248,7 @@ static asio::awaitable<void> on_pick_up_item_generic(
} }
try { 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&) { } catch (const std::out_of_range&) {
// Inventory is full; put the item back where it was // 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", 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)) { if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state(); 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); l->log.info_f("Player {} picked up {:08X} ({})", client_id, item_id, name);
c->print_inventory(); c->print_inventory();
} }
@@ -2285,20 +2280,20 @@ static asio::awaitable<void> on_pick_up_item_generic(
uint32_t pi = fi->data.primary_identifier(); uint32_t pi = fi->data.primary_identifier();
bool should_send_game_notif, should_send_global_notif; bool should_send_game_notif, should_send_global_notif;
if (is_v1_or_v2(c->version()) && (c->version() != Version::GC_NTE)) { 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_game_notif = s->data->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_global_notif = s->data->notify_server_for_item_primary_identifiers_v1_v2.count(pi);
} else if (!is_v4(c->version())) { } else if (!is_v4(c->version())) {
should_send_game_notif = s->notify_game_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->notify_server_for_item_primary_identifiers_v3.count(pi); should_send_global_notif = s->data->notify_server_for_item_primary_identifiers_v3.count(pi);
} else { } else {
should_send_game_notif = s->notify_game_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->notify_server_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) { if (should_send_game_notif || should_send_global_notif) {
std::string p_name = p->disp.visual.name.decode(); 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_ingame = s->data->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_http = s->data->describe_item(c->version(), fi->data);
if (s->http_server) { if (s->http_server) {
auto message = std::make_shared<phosg::JSON>(phosg::JSON::dict({ 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 // 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. // break the reference, so we don't want to accidentally use it again after that.
const auto& item = p->inventory.items[index].data; 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); 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 // 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; 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; 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); 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 // 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. // to do that.
if (c->version() == Version::BB_V4) { 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)) { 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]) { for (auto& item : c->bb_shop_contents[cmd.shop_type]) {
item.id = 0xFFFFFFFF; 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); 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(); auto s = c->require_server_state();
if (is_ep3(c->version())) { if (is_ep3(c->version())) {
const auto& cmd = msg.check_size_t<G_PrivateWordSelect_Ep3_6xBD>(); 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()); std::string from_name = c->character_file()->disp.visual.name.decode(c->language());
static const std::string whisper_text = "(whisper)"; 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 } 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); 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 // 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 // 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); send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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", l->log.info_f("Player {} deposited item {:08X} (x{}) ({}) in the bank",
c->lobby_client_id, cmd.item_id, cmd.item_amount, name); c->lobby_client_id, cmd.item_id, cmd.item_amount, name);
c->print_inventory(); 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 } 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); auto item = bank->remove_item(cmd.item_id, cmd.item_amount, limits);
item.id = l->generate_item_id(c->lobby_client_id); item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, limits); p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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", l->log.info_f("Player {} withdrew item {:08X} (x{}) ({}) from the bank",
c->lobby_client_id, item.id, cmd.item_amount, name); c->lobby_client_id, item.id, cmd.item_amount, name);
c->print_inventory(); c->print_inventory();
@@ -2991,7 +2986,7 @@ static void on_entity_drop_item_request(std::shared_ptr<Client> c, SubcommandMes
if (res.item.empty()) { if (res.item.empty()) {
l->log.info_f("No item was created"); l->log.info_f("No item was created");
} else { } 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); l->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
if (drop_mode == ServerDropMode::SERVER_DUPLICATE) { if (drop_mode == ServerDropMode::SERVER_DUPLICATE) {
for (const auto& lc : l->clients) { 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()) { if (res.item.empty()) {
l->log.info_f("No item was created for {}", lc->channel->name); l->log.info_f("No item was created for {}", lc->channel->name);
} else { } 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); 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); res.item.id = l->generate_item_id(0xFF);
l->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for {}", 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) { static void send_max_level_notification_if_needed(std::shared_ptr<Client> c) {
auto s = c->require_server_state(); auto s = c->require_server_state();
if (!s->notify_server_for_max_level_achieved) { if (!s->data->notify_server_for_max_level_achieved) {
return; return;
} }
@@ -3886,7 +3881,7 @@ static void on_level_up(std::shared_ptr<Client> c, SubcommandMessage& msg) {
if (is_pre_v1(c->version())) { if (is_pre_v1(c->version())) {
msg.check_size_t<G_ChangePlayerLevel_DCNTE_6x30>(); msg.check_size_t<G_ChangePlayerLevel_DCNTE_6x30>();
auto s = c->require_server_state(); 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); 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.atp += incrs.atp;
p->disp.stats.char_stats.mst += incrs.mst; 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; bool leveled_up = false;
do { 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) { if (p->disp.stats.exp >= level.exp) {
leveled_up = true; leveled_up = true;
level.apply(p->disp.stats.char_stats); 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& inventory = p->inventory;
const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)]; 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; uint8_t special_id = 0;
if (((weapon.data.data1[1] < 0x0A) && (weapon.data.data1[2] < 0x05)) || 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); Episode episode = episode_for_area(area);
auto type = ene_st->type(c->version(), area, l->difficulty, l->event); auto type = ene_st->type(c->version(), area, l->difficulty, l->event);
uint32_t enemy_exp = base_exp_for_enemy_type( 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 // 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. // 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; auto& inventory = c->character_file()->inventory;
for (size_t z = 0; z < inventory.num_items; z++) { for (size_t z = 0; z < inventory.num_items; z++) {
auto& item = inventory.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; size_t new_kill_count = item.data.get_kill_count() + 1;
item.data.set_kill_count(new_kill_count); item.data.set_kill_count(new_kill_count);
c->log.info_f("Item {:08X} kill count updated to {}", item.data.id, 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); Episode episode = episode_for_area(area);
auto type = ene_st->type(c->version(), area, l->difficulty, l->event); auto type = ene_st->type(c->version(), area, l->difficulty, l->event);
double base_exp = base_exp_for_enemy_type( 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 // 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, // 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.data1[0] = 0x04;
item.data2d = cmd.amount; item.data2d = cmd.amount;
item.id = l->generate_item_id(c->lobby_client_id); 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); 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>(); const auto& cmd = msg.check_size_t<G_QuestCreateItem_BB_6xCA>();
auto s = c->require_server_state(); auto s = c->require_server_state();
auto l = c->require_lobby(); 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; ItemData item;
item = cmd.item_data; 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); c->character_file()->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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", l->log.info_f("Player {} created inventory item {:08X} ({}) via quest command",
c->lobby_client_id, item.id, name); c->lobby_client_id, item.id, name);
c->print_inventory(); 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&) { } catch (const std::out_of_range&) {
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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", 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); 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 s = c->require_server_state();
auto p = c->character_file(); 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); auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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}", 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->lobby_client_id, cmd.header.client_id, cmd.item_id, name, cmd.amount, cmd.target_guild_card_number);
c->print_inventory(); 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 s = c->require_server_state();
auto p = c->character_file(); 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); auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
size_t amount = item.stack_size(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); s->team_index->add_member_points(c->login->account->account_id, points * amount);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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", 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->lobby_client_id, cmd.header.client_id, cmd.item_id, name, amount, points, amount, points * amount);
c->print_inventory(); 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 s = c->require_server_state();
auto p = c->character_file(); 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)) { 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} ({})", l->log.info_f("Player {} destroyed inventory item {}:{:08X} ({})",
c->lobby_client_id, cmd.header.client_id, cmd.item_id, name); c->lobby_client_id, cmd.header.client_id, cmd.item_id, name);
c->print_inventory(); 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); c->lobby_client_id, cmd.item_id);
} else { } 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); 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 // 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"); throw std::runtime_error("accepted item ID does not match previous identify request");
} }
auto s = c->require_server_state(); 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); send_create_inventory_item_to_lobby(c, c->lobby_client_id, c->bb_identify_result);
c->bb_identify_result.clear(); 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 s = c->require_server_state();
auto p = c->character_file(); 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()));
size_t price = (s->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount; size_t price = (s->data->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount;
p->add_meseta(price); p->add_meseta(price);
if (l->log.should_log(phosg::LogLevel::L_INFO)) { 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", l->log.info_f("Player {} sold inventory item {:08X} ({}) for {} Meseta",
c->lobby_client_id, cmd.item_id, name, price); c->lobby_client_id, cmd.item_id, name, price);
c->print_inventory(); 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>(); const auto& cmd = msg.check_size_t<G_BuyShopItem_BB_6xB7>();
auto s = c->require_server_state(); 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; ItemData item;
item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index); 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)) { if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto s = c->require_server_state(); 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); l->log.info_f("Player {} purchased item {:08X} ({}) for {} meseta", c->lobby_client_id, item.id, name, price);
c->print_inventory(); 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())) { if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index); 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(); 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(); auto lp = lc->character_file();
uint32_t target_level = std::min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199); uint32_t target_level = std::min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199);
uint32_t before_exp = lp->disp.stats.exp; 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)) { if ((lp->disp.stats.exp > before_exp) && (lc->version() == Version::BB_V4)) {
send_give_experience(lc, lp->disp.stats.exp - before_exp, 0xFFFF); send_give_experience(lc, lp->disp.stats.exp - before_exp, 0xFFFF);
send_level_up(lc); send_level_up(lc);
@@ -4718,7 +4713,7 @@ static void on_battle_tech_level_up(std::shared_ptr<Client> c, SubcommandMessage
if (lc) { if (lc) {
auto s = c->require_server_state(); auto s = c->require_server_state();
auto lp = lc->character_file(); 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++) { for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) {
size_t level = lp->get_technique_level(tech_num); size_t level = lp->get_technique_level(tech_num);
if (level != 0xFF) { 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())) { if (is_v4(lc->version())) {
lc->change_bank(lc->bb_character_index); 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"); lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true); 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 { try {
auto p = c->character_file(); 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; ItemData new_item = cmd.replace_item;
assert_quest_item_create_allowed(l, new_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 s = c->require_server_state();
auto p = c->character_file(); 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); send_destroy_item_to_lobby(c, item.id, 1);
item.wrap(*s->item_stack_limits(c->version()), cmd.present_color); item.wrap(*s->data->item_stack_limits(c->version()), cmd.present_color);
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); 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 { try {
auto p = c->character_file(); 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; ItemData new_item = cmd.new_item;
assert_quest_item_create_allowed(l, 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>(); const auto& cmd = msg.check_size_t<G_AddSRankWeaponSpecial_BB_6xD8>();
auto s = c->require_server_state(); 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 { try {
auto p = c->character_file(); 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; item.data1[z] = r.min;
} }
auto s = c->require_server_state(); 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); item.enforce_stack_size_limits(limits);
uint32_t slt_item_id = p->inventory.items[currency_index].data.id; 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 s = c->require_server_state();
auto p = c->character_file(); auto p = c->character_file();
size_t index = p->inventory.find_item_by_primary_identifier(0x03100200); 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); send_destroy_item_to_lobby(c, item.id, 1);
l->drop_mode = ServerDropMode::DISABLED; l->drop_mode = ServerDropMode::DISABLED;
l->allowed_drop_modes = (1 << static_cast<uint8_t>(l->drop_mode)); // DISABLED only 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); size_t count = (cmd.type > 0x03) ? 1 : (static_cast<size_t>(l->difficulty) + 1);
c->log.info_f("Creating {} F95E result items", count); c->log.info_f("Creating {} F95E result items", count);
for (size_t z = 0; z < count; z++) { 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()) { if (results.empty()) {
throw std::runtime_error("invalid result type"); 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) { } else if (item.data1[0] == 0x00) {
item.data1[4] |= 0x80; // Unidentified item.data1[4] |= 0x80; // Unidentified
} else { } 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); 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 s = c->require_server_state();
auto p = c->character_file(); 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()) { if (result.second.empty()) {
throw std::runtime_error("invalid result index"); 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; bool failed = false;
ItemData ticket_item; ItemData ticket_item;
@@ -5309,7 +5304,7 @@ static void on_quest_F960_result_bb(std::shared_ptr<Client> c, SubcommandMessage
ItemData item; ItemData item;
for (size_t num_failures = 0; num_failures <= cmd.result_tier; num_failures++) { for (size_t num_failures = 0; num_failures <= cmd.result_tier; num_failures++) {
size_t tier = 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; uint64_t probability = results.base_probability + num_failures * results.probability_upgrade;
if (l->rand_crypt->next() <= probability) { if (l->rand_crypt->next() <= probability) {
c->log.info_f("Tier {} yielded a prize", tier); 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()) { if (item.empty()) {
c->log.info_f("Choosing result from failure tier"); 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()]; item = result_items[l->rand_crypt->next() % result_items.size()];
} }
if (item.empty()) { 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); item.id = l->generate_item_id(c->lobby_client_id);
// If it's a weapon, make it unidentified // 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))) { if ((item.data1[0] == 0x00) && (item_parameter_table->is_item_rare(item) || (item.data1[4] != 0))) {
item.data1[4] |= 0x80; 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 // 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; bool added_to_inventory;
try { 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; added_to_inventory = true;
} catch (const std::out_of_range&) { } 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 // 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)) { 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)"); c->log.info_f("Awarded item {} {}", name, added_to_inventory ? "in inventory" : "on ground (inventory is full)");
} }
if (added_to_inventory) { 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 s = c->require_server_state();
auto p = c->character_file(); 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; ItemData new_item = cmd.replace_item;
assert_quest_item_create_allowed(l, new_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; uint32_t payment_primary_identifier = cmd.payment_type ? 0x03100100 : 0x03100000;
size_t payment_index = p->inventory.find_item_by_primary_identifier(payment_primary_identifier); size_t payment_index = p->inventory.find_item_by_primary_identifier(payment_primary_identifier);
auto& payment_item = p->inventory.items[payment_index].data; 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"); 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( 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); send_destroy_item_to_lobby(c, removed_payment_item.id, cmd.payment_count);
item.data1[attribute_index] = cmd.attribute; 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; break;
} }
default: 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) ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
: state(state), : state(state),
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
commands_sent(0), commands_sent(0),
bytes_sent(0), bytes_sent(0),
commands_received(0), commands_received(0),
bytes_received(0), bytes_received(0),
idle_timeout_timer(*this->state->io_context), idle_timeout_timer(*this->state->io_context) {
run_failed(false) {
std::shared_ptr<Event> parsing_command = nullptr; std::shared_ptr<Event> parsing_command = nullptr;
size_t line_num = 0; size_t line_num = 0;
@@ -364,16 +362,16 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
} }
if (line == "### use psov2 crypt") { 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") { 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 ")) { if (line.starts_with("### cc ")) {
// ### cc $<chat command> // ### cc $<chat command>
if (this->clients.size() != 1) { if (this->clients.size() != 1) {
throw std::runtime_error(std::format( 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)); line_num));
} }
std::shared_ptr<Event> event; 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)); event->data = encode_chat_message(c->version, line.substr(7));
num_events++; num_events++;
} catch (const std::exception& e) { } 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; 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)); event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
num_events++; num_events++;
} catch (const std::exception& e) { } 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; continue;
@@ -412,21 +410,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
auto tokens = phosg::split(line, ' '); auto tokens = phosg::split(line, ' ');
if (!tokens[8].starts_with("C-")) { 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); uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
auto listen_tokens = phosg::split(tokens[10], '-'); auto listen_tokens = phosg::split(tokens[10], '-');
if (listen_tokens.size() < 4) { if (listen_tokens.size() < 4) {
throw std::runtime_error(std::format( 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); uint16_t port = stoul(listen_tokens[1], nullptr, 10);
Version version = phosg::enum_for_name<Version>(listen_tokens[2]); Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
auto c = std::make_shared<Client>(state->io_context, client_id, port, version); auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
if (!this->clients.emplace(c->id, c).second) { 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); this->create_event(Event::Type::CONNECT, c, line_num);
num_events++; num_events++;
@@ -438,21 +436,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
if (offset != std::string::npos) { if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' '); auto tokens = phosg::split(line, ' ');
if (tokens.size() < 11) { 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-")) { 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); uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
try { try {
auto& c = this->clients.at(client_id); auto& c = this->clients.at(client_id);
if (c->disconnect_event.get()) { 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); c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
num_events++; num_events++;
} catch (const std::out_of_range&) { } 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; continue;
} }
@@ -466,7 +464,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
if (offset != std::string::npos) { if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' '); auto tokens = phosg::split(line, ' ');
if (tokens.size() < 10) { 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"); bool from_client = (tokens[6] == "Received");
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16); 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); from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
num_events++; num_events++;
} catch (const std::out_of_range&) { } 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; continue;
} }
@@ -494,8 +492,12 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
} }
asio::awaitable<void> ReplaySession::run() { 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 { try {
replay_log.info_f("Starting replay");
while (this->first_event) { while (this->first_event) {
if (!this->first_event->complete) { if (!this->first_event->complete) {
auto& c = this->clients.at(this->first_event->client_id); auto& c = this->clients.at(this->first_event->client_id);
@@ -505,15 +507,15 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::CONNECT: { case Event::Type::CONNECT: {
if (c->channel->connected()) { if (c->channel->connected()) {
throw std::runtime_error(std::format( 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 { 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&) { } catch (const std::out_of_range&) {
throw std::runtime_error(std::format( 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); 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); this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
} else { } else {
throw std::runtime_error(std::format( 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; break;
} }
@@ -535,7 +537,7 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::SEND: case Event::Type::SEND:
if (!c->channel->connected()) { if (!c->channel->connected()) {
throw std::runtime_error(std::format( 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); c->channel->send(this->first_event->data);
this->commands_sent++; this->commands_sent++;
@@ -544,8 +546,8 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::RECEIVE: { case Event::Type::RECEIVE: {
if (!c->channel->connected()) { if (!c->channel->connected()) {
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client", throw std::runtime_error(std::format(
this->first_event->line_num)); "(ev-line {}) Receive event on non-connected client", this->first_event->line_num));
} }
if (c->receive_events.front() != this->first_event) { if (c->receive_events.front() != this->first_event) {
throw std::logic_error("Client receive events are out of order"); 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(); this->bytes_received += full_command.size();
if (c->receive_events.empty()) { if (c->receive_events.empty()) {
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); std::string data_str = phosg::format_data(full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error("received unexpected command for client"); throw std::runtime_error(std::format("Received unexpected command for client:\n{}", data_str));
} }
auto& ev = c->receive_events.front(); auto& ev = c->receive_events.front();
if ((full_command.size() != ev->data.size()) && !ev->allow_size_disparity) { if ((full_command.size() != ev->data.size()) && !ev->allow_size_disparity) {
replay_log.error_f("Expected command:"); std::string expected_data = phosg::format_data(
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:"); std::string received_data = phosg::format_data(
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); 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)); 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++) { 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])) { if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
replay_log.error_f("Expected command:"); std::string expected_data = phosg::format_data(
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:"); std::string received_data = phosg::format_data(
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS); 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)); 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 // TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
// first one here. // first one here.
c->channel->crypt_in = std::make_shared<PSOBBEncryption>( 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>( 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; break;
default: default:
throw std::logic_error("unsupported encryption version"); throw std::logic_error("Unsupported encryption version");
} }
break; break;
} }
default: default:
throw std::logic_error("unhandled event type"); throw std::logic_error("Unhandled event type");
} }
this->first_event->complete = true; this->first_event->complete = true;
} }
@@ -646,13 +652,12 @@ asio::awaitable<void> ReplaySession::run() {
} }
} }
} catch (const std::exception& e) { } 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) { 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 { } 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) { for (auto& [_, c] : this->clients) {
@@ -660,14 +665,18 @@ asio::awaitable<void> ReplaySession::run() {
c->channel->disconnect(); 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 // 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)); co_await async_sleep(std::chrono::seconds(2));
replay_log.info_f("Replay complete: {} commands sent ({} bytes), {} commands received ({} bytes)", 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->commands_sent, this->bytes_sent, this->commands_received, this->bytes_received);
} }
this->idle_timeout_timer.cancel();
} }
void ReplaySession::reschedule_idle_timeout() { void ReplaySession::reschedule_idle_timeout() {
+6 -4
View File
@@ -21,8 +21,9 @@ public:
~ReplaySession() = default; ~ReplaySession() = default;
asio::awaitable<void> run(); asio::awaitable<void> run();
inline bool failed() const {
return this->run_failed; inline std::string failure_str() const {
return this->failure;
} }
private: private:
@@ -62,7 +63,8 @@ private:
}; };
std::shared_ptr<ServerState> state; 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; std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
@@ -75,7 +77,7 @@ private:
size_t bytes_received; size_t bytes_received;
asio::steady_timer idle_timeout_timer; 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); 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); send_command_t(c, use_secondary_message ? 0x9B : 0x03, 0x00, cmd);
c->bb_detector_crypt = std::make_shared<PSOBBMultiKeyDetectorEncryption>( 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, bb_crypt_initial_client_commands,
cmd.basic_cmd.client_key.data(), cmd.basic_cmd.client_key.data(),
sizeof(cmd.basic_cmd.client_key)); 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(); auto s = c->require_server_state();
if (!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { 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; 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); auto call1_res = co_await send_function_call(c, fn, label_writes, nullptr, 0, 0x80000000, 8, 0x7F2734EC);
try { try {
@@ -345,7 +345,7 @@ asio::awaitable<void> prepare_client_for_patches(std::shared_ptr<Client> c) {
} catch (const std::out_of_range&) { } catch (const std::out_of_range&) {
c->log.info_f("Could not detect specific version from header checksum {:08X}", call1_res.checksum); 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->log.info_f("Client cache behavior patched");
c->set_flag(Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH); 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) && if ((arch != ClientFunctionIndex::Function::Architecture::UNKNOWN) &&
specific_version_is_indeterminate(c->specific_version)) { 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->specific_version = vers_detect_res.return_value;
c->log.info_f("Version detected as {:08X}", c->specific_version); 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::GC_EP3:
case Version::BB_V4: { case Version::BB_V4: {
auto s = c->require_server_state(); 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::HAS_SEND_FUNCTION_CALL) ||
!c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE)) { !c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE)) {
co_return false; 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); co_await prepare_client_for_patches(c);
try { 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}}; std::unordered_map<std::string, uint32_t> label_writes{{"size", size}};
co_await send_function_call(c, fn, label_writes, data, size); co_await send_function_call(c, fn, label_writes, data, size);
auto l = echo_to_lobby ? c->lobby.lock() : nullptr; 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 // Determine the necessary start address for the data
std::unordered_map<std::string, uint32_t> label_writes{{"address", 0x80000034}}; // ArenaHigh from GC globals std::unordered_map<std::string, uint32_t> label_writes{{"address", 0x80000034}}; // ArenaHigh from GC globals
auto addr_ret = co_await send_function_call( 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); uint32_t dol_base_addr = (addr_ret.return_value - dol->data.size()) & (~3);
// Write the file in multiple chunks // 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); std::string data_to_send = dol->data.substr(offset, bytes_to_send);
auto s = c->require_server_state(); 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}}; 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()); 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 // 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}}; label_writes = {{"dol_base_ptr", dol_base_addr}};
co_await send_function_call(c, fn, label_writes); co_await send_function_call(c, fn, label_writes);
// The client will stop running PSO after this, so disconnect them // 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(); auto s = c->require_server_state();
std::vector<S_StreamFileIndexEntry_BB_01EB> entries; 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(); auto& e = entries.emplace_back();
e.size = sf_entry.size; e.size = sf_entry.size;
e.checksum = sf_entry.checksum; 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; S_StreamFileChunk_BB_02EB chunk_cmd;
chunk_cmd.chunk_index = chunk_index; chunk_cmd.chunk_index = chunk_index;
size_t offset = sizeof(chunk_cmd.data) * 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"); 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)); 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->bb_stream_file->data.data() + offset), bytes, 0); 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; size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes;
cmd_size = (cmd_size + 3) & ~3; 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.size = sizeof(cmd.reconnect_command_header) + sizeof(cmd.reconnect_command);
cmd.reconnect_command_header.command = 0x19; cmd.reconnect_command_header.command = 0x19;
cmd.reconnect_command_header.flag = 0x00; cmd.reconnect_command_header.flag = 0x00;
cmd.reconnect_command.address = s->connect_address_for_client(c); cmd.reconnect_command.address = s->data->connect_address_for_client(c);
cmd.reconnect_command.port = s->game_server_port_for_version(c->version()); cmd.reconnect_command.port = s->data->game_server_port_for_version(c->version());
cmd.reconnect_command.unused = 0; cmd.reconnect_command.unused = 0;
std::string location_string; std::string location_string;
if (result_lobby->is_game()) { 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()) { } 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 { } 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.location_string.encode(location_string, c->language());
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY; 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.item_id = 0x00000000;
e.difficulty_tag = 0x00; e.difficulty_tag = 0x00;
e.num_players = 0x00; e.num_players = 0x00;
e.name.encode(s->name, c->language()); e.name.encode(s->data->name, c->language());
e.episode = 0x00; e.episode = 0x00;
e.flags = 0x04; e.flags = 0x04;
} }
@@ -1623,7 +1623,7 @@ void send_quest_categories_menu_t(std::shared_ptr<Client> c, QuestMenuType menu_
std::vector<EntryT> entries; std::vector<EntryT> entries;
auto s = c->require_server_state(); 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(); auto& e = entries.emplace_back();
e.menu_id = cat->use_ep2_icon() ? MenuID::QUEST_CATEGORIES_EP2 : MenuID::QUEST_CATEGORIES_EP1_EP3_EP4; e.menu_id = cat->use_ep2_icon() ? MenuID::QUEST_CATEGORIES_EP2 : MenuID::QUEST_CATEGORIES_EP1_EP3_EP4;
e.item_id = cat->category_id; 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; std::vector<S_QuestMenuEntry_DC_GC_A2_A4> entries;
auto s = c->require_server_state(); 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)) { if (cat->check_visibility_flag(vis_flag)) {
auto& e = entries.emplace_back(); auto& e = entries.emplace_back();
e.menu_id = MenuID::QUEST_CATEGORIES_EP1_EP3_EP4; 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; : Episode3::MapIndex::VisibilityFlag::ONLINE_FINAL;
auto s = c->require_server_state(); 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)) { if (!category->check_visibility_flag(vis_flag)) {
throw std::runtime_error("category is not visible to this client"); 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]; auto& p = cmd.players[z];
populate_lobby_data_for_client(p.lobby_data, wc, c); populate_lobby_data_for_client(p.lobby_data, wc, c);
p.inventory = wc_p->inventory; 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 = wc_p->disp.to_v123<false>(c->language(), p.inventory.language);
p.disp.enforce_lobby_join_limits_for_version(c->version()); 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(); : wc_p->disp.stats.level.load();
e.name_color = wc_p->disp.visual.sh.name_color; 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) { if (name_color) {
p.disp.visual.sh.name_color = name_color; p.disp.visual.sh.name_color = name_color;
e.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]; auto& p = cmd.players[client_id];
p.lobby_data = entry.lobby_data; p.lobby_data = entry.lobby_data;
p.inventory = entry.inventory; 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 = entry.disp;
p.disp.enforce_lobby_join_limits_for_version(c->version()); 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(); : other_p->disp.stats.level.load();
cmd_e.name_color = other_p->disp.visual.sh.name_color; 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) { if (name_color) {
cmd_p.disp.visual.sh.name_color = name_color; cmd_p.disp.visual.sh.name_color = name_color;
cmd_e.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 other_p = lc->character_file();
auto& cmd_p = cmd.players_ep3[x]; auto& cmd_p = cmd.players_ep3[x];
cmd_p.inventory = other_p->inventory; 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>( cmd_p.disp = convert_player_disp_data<PlayerDispDataV123>(
other_p->disp, c->language(), other_p->inventory.language); other_p->disp, c->language(), other_p->inventory.language);
cmd_p.disp.enforce_lobby_join_limits_for_version(c->version()); 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) { if (name_color) {
cmd_p.disp.visual.sh.name_color = 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++]; auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c); populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory; 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) { 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); e.disp = convert_player_disp_data<DispDataT>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
} else { } else {
e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language); e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version()); 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) { if (name_color) {
e.disp.visual.sh.name_color = name_color; e.disp.visual.sh.name_color = name_color;
if (is_v1_or_v2(c->version())) { 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++]; auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c); populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory; 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 = convert_player_disp_data<PlayerDispDataV123>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version()); 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) { if (name_color) {
e.disp.visual.sh.name_color = 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++]; auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c); populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory; 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) { 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); e.disp = convert_player_disp_data<PlayerDispDataV123>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
} else { } else {
e.disp = convert_player_disp_data<PlayerDispDataV123>(lp->disp, c->language(), lp->inventory.language); e.disp = convert_player_disp_data<PlayerDispDataV123>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits_for_version(c->version()); 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) { if (name_color) {
e.disp.visual.sh.name_color = name_color; e.disp.visual.sh.name_color = name_color;
e.disp.visual.sh.compute_name_color_checksum(); e.disp.visual.sh.compute_name_color_checksum();
@@ -2459,7 +2459,7 @@ asio::awaitable<GetPlayerInfoResult> send_get_player_info(std::shared_ptr<Client
} }
try { try {
auto s = c->require_server_state(); 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); send_function_call(c->channel, c->enabled_flags, fn);
c->function_call_response_queue.emplace_back(std::make_shared<AsyncPromise<C_ExecuteCodeResult_B3>>()); c->function_call_response_queue.emplace_back(std::make_shared<AsyncPromise<C_ExecuteCodeResult_B3>>());
full_req_sent = true; 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.target_client_id = c->lobby_client_id;
cmd.item_count = items.size(); 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++) { for (size_t x = 0; x < items.size(); x++) {
cmd.item_datas[x] = items[x]; cmd.item_datas[x] = items[x];
cmd.item_datas[x].encode_for_version(c->version(), item_parameter_table); 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.room_id = 0;
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++); fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
fi.item = item->data; 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); floor_items_w.put(fi);
decompressed_header.floor_item_count_per_floor.at(floor)++; 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); 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}; 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); 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); uint8_t subcommand = get_pre_v1_subcommand(ch->version, 0x51, 0x58, 0x5F);
G_DropItem_PC_V3_BB_6x5F cmd = { G_DropItem_PC_V3_BB_6x5F cmd = {
{{subcommand, 0x0B, 0x0000}, {floor, source_type, entity_index, pos, 0, 0, item}}, 0}; {{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)); ch->send(0x60, 0x00, &cmd, sizeof(cmd));
} }
} }
@@ -3093,7 +3093,7 @@ void send_drop_stacked_item_to_channel(
const VectorXZF& pos) { const VectorXZF& pos) {
uint8_t subcommand = get_pre_v1_subcommand(ch->version, 0x4F, 0x56, 0x5D); 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}; 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)); 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)) { if (!c->check_flag(Client::Flag::HAS_EP3_CARD_DEFS)) {
auto s = c->require_server_state(); auto s = c->require_server_state();
const auto& data = (c->version() == Version::GC_EP3_NTE) const auto& data = (c->version() == Version::GC_EP3_NTE)
? s->ep3_card_index_trial->get_compressed_definitions() ? s->data->ep3_card_index_trial->get_compressed_definitions()
: s->ep3_card_index->get_compressed_definitions(); : s->data->ep3_card_index->get_compressed_definitions();
phosg::StringWriter w; phosg::StringWriter w;
w.put_u32l(data.size()); 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) { void send_ep3_rank_update(std::shared_ptr<Client> c) {
auto s = c->require_server_state(); auto s = c->require_server_state();
uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_current_meseta; uint32_t current_meseta = s->data->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 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}; S_RankUpdate_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF};
send_command_t(c, 0xB7, 0x00, cmd); send_command_t(c, 0xB7, 0x00, cmd);
} }
@@ -3383,7 +3383,7 @@ void send_ep3_confirm_tournament_entry(
if (tourn) { if (tourn) {
auto s = c->require_server_state(); auto s = c->require_server_state();
cmd.tournament_name.encode(tourn->get_name(), c->language()); 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 // TODO: Fill this in appropriately when we support scheduled start times
cmd.start_time.encode("Unknown", c->language()); cmd.start_time.encode("Unknown", c->language());
auto& teams = tourn->all_teams(); 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); add_entries_for_team(match->preceding_b->winner_team, 2);
if ((c->version() != Version::GC_EP3_NTE) && 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; uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); 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.winner_team_id = (match->preceding_b->winner_team == match->winner_team);
cmd.meseta_amount = meseta_reward; cmd.meseta_amount = meseta_reward;
cmd.meseta_reward_text.encode("You got %s meseta!", Language::ENGLISH); 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; uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
} }
send_command_t(lc, 0xC9, 0x00, cmd); 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 {}", send_text_message_fmt(l, "$C5TOURN/{:X}/{} WIN {}",
tourn->get_menu_item_id(), match->round_num, tourn->get_menu_item_id(), match->round_num,
match->winner_team == match->preceding_a->winner_team ? 'A' : 'B'); 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; cmd.total_spectators = total_spectators;
for (auto c : l->clients) { for (auto c : l->clients) {
if (c) { 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; G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key); 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); cmd.text.encode(text, Language::ENGLISH);
for (auto c : watcher_l->clients) { for (auto c : watcher_l->clients) {
if (c) { 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; G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1; uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key); 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); c->log.info_f("Sending quest file chunk {}:{}", filename, chunk_index);
const auto& s = c->require_server_state(); 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> 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) { void send_ep3_card_auction(std::shared_ptr<Lobby> l) {
auto s = l->require_server_state(); auto s = l->require_server_state();
if ((s->ep3_card_auction_points == 0) || if ((s->data->ep3_card_auction_points == 0) ||
(s->ep3_card_auction_min_size == 0) || (s->data->ep3_card_auction_min_size == 0) ||
(s->ep3_card_auction_max_size == 0)) { (s->data->ep3_card_auction_max_size == 0)) {
throw std::runtime_error("card auctions are not configured on this server"); throw std::runtime_error("card auctions are not configured on this server");
} }
uint16_t num_cards; uint16_t num_cards;
if (s->ep3_card_auction_min_size == s->ep3_card_auction_max_size) { if (s->data->ep3_card_auction_min_size == s->data->ep3_card_auction_max_size) {
num_cards = s->ep3_card_auction_min_size; num_cards = s->data->ep3_card_auction_min_size;
} else { } else {
num_cards = s->ep3_card_auction_min_size + num_cards = s->data->ep3_card_auction_min_size +
(phosg::random_object<uint16_t>() % (s->ep3_card_auction_max_size - s->ep3_card_auction_min_size + 1)); (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); 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; 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; distribution_size += e.probability;
} }
S_StartCardAuction_Ep3_EF cmd; 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++) { for (size_t z = 0; z < num_cards; z++) {
uint64_t v = phosg::random_object<uint64_t>() % distribution_size; 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) { if (v >= e.probability) {
v -= e.probability; v -= e.probability;
} else { } 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 "CommonItemSet.hh"
#include "DNSServer.hh" #include "DNSServer.hh"
#include "DOLFileIndex.hh" #include "DOLFileIndex.hh"
#include "DataIndex.hh"
#include "Episode3/DataIndexes.hh" #include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh" #include "Episode3/Tournament.hh"
#include "GSLArchive.hh" #include "GSLArchive.hh"
@@ -37,291 +38,28 @@ class GameServer;
class IPStackSimulator; class IPStackSimulator;
class HTTPServer; class HTTPServer;
struct PortConfiguration { class ServerState : public std::enable_shared_from_this<ServerState> {
std::string name; public:
std::string addr; // Blank = listen on all interfaces (default) std::shared_ptr<DataIndex> data;
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;
};
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::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 default_lobbies_created = false;
bool is_replay = 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_psov2_rand_crypt = false; // Used in some tests
bool use_legacy_item_random_behavior = 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; 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<AccountIndex> account_index;
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
std::shared_ptr<TeamIndex> team_index; 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::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; 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::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<IPStackSimulator> ip_stack_simulator;
std::shared_ptr<DNSServer> dns_server; std::shared_ptr<DNSServer> dns_server;
std::shared_ptr<GameServer> game_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; std::unordered_map<uint32_t, ProxySession::PersistentConfig> proxy_persistent_configs;
explicit ServerState(const std::string& config_filename = "", bool is_replay = false); static std::shared_ptr<ServerState> create_shared(std::shared_ptr<DataIndex> data_index, bool is_replay);
ServerState(const ServerState&) = delete; std::shared_ptr<ServerState> clone_shared();
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
void add_client_to_available_lobby(std::shared_ptr<Client> c, bool allow_games); void add_client_to_available_lobby(std::shared_ptr<Client> c, bool allow_games);
void remove_client_from_lobby(std::shared_ptr<Client> c); 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, bool send_join_notification = true,
ssize_t required_client_id = -1); ssize_t required_client_id = -1);
void send_lobby_join_notifications(std::shared_ptr<Lobby> l, void send_lobby_join_notifications(std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
std::shared_ptr<Client> joining_client);
std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id); std::shared_ptr<Lobby> find_lobby(uint32_t lobby_id);
std::vector<std::shared_ptr<Lobby>> all_lobbies(); 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( std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr, uint64_t account_id = 0, std::shared_ptr<Lobby> l = nullptr); 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 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_accounts();
void load_teams(); 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_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 reset_between_replays();
void disconnect_all_banned_clients(); 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, ' '); auto types = phosg::split(args.args, ' ');
for (const auto& type : types) { for (const auto& type : types) {
if (type == "all") { 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") { } else if (type == "bb-keys") {
args.s->load_bb_private_keys(); args.s->data->load_bb_private_keys();
} else if (type == "accounts") { } else if (type == "accounts") {
args.s->load_accounts(); args.s->load_accounts();
} else if (type == "maps") { } else if (type == "maps") {
args.s->load_maps(); args.s->data->load_maps();
} else if (type == "patch-files") { } else if (type == "patch-files") {
args.s->load_patch_indexes(); args.s->data->load_patch_indexes();
} else if (type == "ep3-cards") { } else if (type == "ep3-cards") {
args.s->load_ep3_cards(); args.s->data->load_ep3_cards();
} else if (type == "ep3-maps") { } else if (type == "ep3-maps") {
args.s->load_ep3_maps(); args.s->data->load_ep3_maps();
} else if (type == "ep3-tournaments") { } else if (type == "ep3-tournaments") {
args.s->load_ep3_tournament_state(); args.s->load_ep3_tournament_state();
} else if (type == "functions") { } else if (type == "functions") {
args.s->compile_functions(); args.s->data->compile_functions();
} else if (type == "dol-files") { } else if (type == "dol-files") {
args.s->load_dol_files(); args.s->data->load_dol_files();
} else if (type == "set-tables") { } else if (type == "set-tables") {
args.s->load_set_data_tables(); args.s->data->load_set_data_tables();
} else if (type == "battle-params") { } else if (type == "battle-params") {
args.s->load_battle_params(); args.s->data->load_battle_params();
args.s->generate_bb_stream_file(); args.s->data->generate_bb_stream_file();
} else if (type == "level-tables") { } else if (type == "level-tables") {
args.s->load_level_tables(); args.s->data->load_level_tables();
args.s->generate_bb_stream_file(); args.s->data->generate_bb_stream_file();
} else if (type == "text-index") { } else if (type == "text-index") {
args.s->load_text_index(); args.s->data->load_text_index();
} else if (type == "word-select") { } else if (type == "word-select") {
args.s->load_word_select_table(); args.s->data->load_word_select_table();
} else if (type == "item-definitions") { } else if (type == "item-definitions") {
args.s->load_item_definitions(); args.s->data->load_item_definitions();
args.s->generate_bb_stream_file(); args.s->data->generate_bb_stream_file();
} else if (type == "item-name-index") { } else if (type == "item-name-index") {
args.s->load_item_name_indexes(); args.s->data->load_item_name_indexes();
} else if (type == "drop-tables") { } else if (type == "drop-tables") {
args.s->load_drop_tables(); args.s->data->load_drop_tables();
} else if (type == "config") { } else if (type == "config") {
args.s->load_config_early(); args.s->data->load_config_early();
args.s->load_config_late(); args.s->data->load_config_late();
args.s->disconnect_all_banned_clients();
args.s->update_default_lobby_events_from_config();
} else if (type == "teams") { } else if (type == "teams") {
args.s->load_teams(); args.s->load_teams();
} else if (type == "quests") { } else if (type == "quests") {
args.s->load_quest_index(); args.s->data->load_quest_index();
} else { } else {
throw std::runtime_error("invalid data type: " + type); throw std::runtime_error("invalid data type: " + type);
} }
@@ -663,7 +667,7 @@ ShellCommand c_announce_mail(
"announce-mail", "announce-mail MESSAGE\n\ "announce-mail", "announce-mail MESSAGE\n\
Send an announcement message via Simple Mail to all players.", Send an announcement message via Simple Mail to all players.",
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> { +[](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>{}; co_return std::deque<std::string>{};
}); });
@@ -698,7 +702,7 @@ ShellCommand c_create_tournament(
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> { +[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string name = get_quoted_string(args.args); std::string name = get_quoted_string(args.args);
std::string map_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); uint32_t num_teams = std::stoul(get_quoted_string(args.args), nullptr, 0);
Episode3::Rules rules; Episode3::Rules rules;
rules.set_defaults(); rules.set_defaults();
@@ -1089,13 +1093,14 @@ ShellCommand c_create_item(
throw std::runtime_error("proxy session is not game leader"); 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; 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->channel, item, c->floor, c->pos);
send_drop_stacked_item_to_channel(args.s, c->proxy_session->server_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); send_text_message(c->channel, "$C7Item created:\n" + name);
co_return std::deque<std::string>{}; co_return std::deque<std::string>{};
}); });
+7 -3
View File
@@ -27,8 +27,10 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
case SIGUSR1: case SIGUSR1:
this->log.info_f("Received SIGUSR1; reloading config.json"); this->log.info_f("Received SIGUSR1; reloading config.json");
try { try {
this->state->load_config_early(); this->state->data->load_config_early();
this->state->load_config_late(); 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"); phosg::fwrite_fmt(stderr, "Configuration update complete\n");
} catch (const std::exception& e) { } catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what()); phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
@@ -38,7 +40,9 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
case SIGUSR2: case SIGUSR2:
this->log.info_f("Received SIGUSR2; reloading config.json and all dependencies"); this->log.info_f("Received SIGUSR2; reloading config.json and all dependencies");
try { 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"); phosg::fwrite_fmt(stderr, "Configuration update complete\n");
} catch (const std::exception& e) { } catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what()); 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 > 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) 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 @ 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) 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 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 0010 | 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | A