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