upstream-master-20260614
CMake / build (macos-latest) (push) Has been cancelled
CMake / build (ubuntu-latest) (push) Has been cancelled

upstream master 20260614
This commit is contained in:
James Osborne
2026-06-14 19:12:29 -04:00
committed by GitHub
47 changed files with 3754 additions and 3467 deletions
+6 -11
View File
@@ -69,6 +69,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
@@ -159,17 +160,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})
+14 -2
View File
@@ -682,8 +682,20 @@ Allow loading corrupted save files
041156D4 4E800020
60 frames per second
This does not adjust any logic or animations; everything just runs faster
3OE1 => 045CDEF8 00000001
This doesn't adjust any logic or animations; everything just runs faster
3OJT => 043F5AC0 38800001
3OJ2 => 043D8550 38800001
3OJ3 => 043DAF58 38800001
3OJ4 => 043DCDF8 38800001
3OJ5 => 043DCBA8 38800001
3OE0 => 043D9820 38800001
3OE1 => 043D9878 38800001
3OE2 => 043DCF78 38800001
3OP0 => 043DBA68 38800001
3SJT => 043567AC 38800001
3SE0 => 0438A804 38800001
3SJ0 => 043897B4 38800001
3SP0 => 0438B6D4 38800001
Show extended item info when targeting a dropped item
(Compiled from the ExtendedItemInfo patch, also written by me)
+63 -62
View File
@@ -143,7 +143,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");
}
@@ -220,7 +220,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);
}
@@ -473,8 +473,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;
}
@@ -526,13 +526,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);
@@ -591,8 +591,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);
@@ -616,7 +616,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));
}
@@ -628,8 +628,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 {
@@ -773,7 +773,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) {
@@ -894,7 +894,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);
@@ -904,28 +904,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") {
@@ -962,7 +962,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") {
@@ -978,7 +978,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));
@@ -1004,7 +1004,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++) {
@@ -1119,7 +1119,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) {
@@ -1167,7 +1167,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) {
@@ -1215,7 +1215,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");
}
@@ -1227,7 +1227,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;
@@ -1238,12 +1238,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);
@@ -1252,8 +1252,8 @@ ChatCommandDefinition cc_item(
} else {
auto l = a.c->require_lobby();
item = s->parse_item_description(a.c->version(), a.text);
item.id = l->generate_item_id(a.c->lobby_client_id);
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)) {
l->add_item(a.c->floor, item, a.c->pos, nullptr, nullptr, (1 << a.c->lobby_client_id));
@@ -1264,7 +1264,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 {
@@ -1353,7 +1353,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());
}
@@ -1543,8 +1543,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;
@@ -1576,7 +1576,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) {
@@ -1712,7 +1712,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);
});
@@ -1773,8 +1773,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);
}
@@ -1790,7 +1790,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);
@@ -1851,7 +1851,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:
@@ -2073,7 +2073,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&) {
@@ -2202,7 +2202,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");
}
@@ -2242,7 +2242,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");
}
@@ -2264,7 +2264,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;
@@ -2301,7 +2301,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");
}
@@ -2345,7 +2345,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);
@@ -2419,7 +2419,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()) {
@@ -2452,7 +2452,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) {
@@ -2499,7 +2499,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,
@@ -2860,7 +2860,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");
@@ -2888,7 +2888,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++) {
@@ -2907,7 +2907,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)) {
@@ -2981,7 +2981,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;
});
@@ -3015,7 +3016,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&) {
@@ -3154,7 +3155,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&) {
@@ -3194,7 +3195,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");
+2 -2
View File
@@ -5,7 +5,7 @@
#include "Client.hh"
const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES{
ChoiceSearchCategory{
.id = 0x0001,
.name = "Level",
@@ -145,4 +145,4 @@ const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
},
},
});
};
+23 -27
View File
@@ -230,7 +230,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 {
@@ -239,7 +239,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);
@@ -249,7 +249,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;
@@ -322,7 +322,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");
@@ -337,7 +337,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");
@@ -350,7 +350,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();
@@ -433,7 +433,9 @@ bool Client::evaluate_quest_availability_expression(
}
auto p = this->character_file();
IntegralExpression::Env env = {
.flags = &p->quest_flags.data.at(static_cast<size_t>(difficulty)),
.section_id = p->disp.visual.sh.section_id,
.difficulty = difficulty,
.flags = &p->quest_flags,
.challenge_records = &p->challenge_records,
.team = this->team(),
.num_players = num_players,
@@ -499,15 +501,15 @@ 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->allow_same_account_concurrent_logins_across_client_sources) {
if (!s->data->allow_same_account_concurrent_logins) {
if (s->data->allow_same_account_concurrent_logins_across_client_sources) {
uint64_t source_key = account_client_source_key(login->account->account_id, this->version());
auto it = s->client_for_account_source.find(source_key);
if ((it != s->client_for_account_source.end()) && (it->second.get() != this)) {
@@ -529,14 +531,8 @@ void Client::set_login(std::shared_ptr<Login> login) {
s->client_for_account.emplace(this->login->account->account_id, this->shared_from_this());
}
}
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
this->log.info_f("Login: {}", this->login->str());
}
}
// System file
std::string Client::system_filename(const std::string& bb_username) {
return std::format("system/players/system_{}.psosys", bb_username);
}
@@ -898,8 +894,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;
@@ -1109,11 +1105,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");
}
@@ -1123,7 +1119,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++) {
@@ -1148,7 +1144,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));
@@ -1158,7 +1154,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) {
@@ -1230,7 +1226,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);
}
}
@@ -1244,7 +1240,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 {
+1 -1
View File
@@ -703,7 +703,7 @@ CommonItemSet::Table::Table(const phosg::StringReader& r, bool is_big_endian, bo
template <bool BE>
void CommonItemSet::Table::parse_itempt_t(const phosg::StringReader& r, bool is_v3) {
const auto& offsets = r.pget<OffsetsT<BE>>(r.pget<U32T<BE>>(r.size() - 0x10));
const auto& offsets = r.pget<RootT<BE>>(r.pget<U32T<BE>>(r.size() - 0x10));
this->base_weapon_type_prob_table = r.pget<parray<uint8_t, 0x0C>>(offsets.base_weapon_type_prob_table_offset);
this->subtype_base_table = r.pget<parray<int8_t, 0x0C>>(offsets.subtype_base_table_offset);
+4 -4
View File
@@ -69,7 +69,7 @@ public:
void parse_itempt_t(const phosg::StringReader& r, bool is_v3);
template <bool BE>
struct OffsetsT {
struct RootT {
// This data structure uses index probability tables in multiple places. An index probability table is a table
// where each entry holds the probability that that entry's index is used. For example, if the armor slot count
// probability table contains [77, 17, 5, 1, 0], this means there is a 77% chance of no slots, 17% chance of 1
@@ -244,9 +244,9 @@ public:
/* 50 */ U32T<BE> box_item_class_prob_table_offset;
// There are several unused fields here.
} __packed_ws_be__(OffsetsT, 0x54);
using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>;
} __packed_ws_be__(RootT, 0x54);
using Root = RootT<false>;
using RootBE = RootT<true>;
};
bool operator==(const CommonItemSet& other) const = default;
+4 -5
View File
@@ -14,8 +14,7 @@
#include "NetworkAddresses.hh"
#include "ServerState.hh"
DNSServer::DNSServer(std::shared_ptr<ServerState> state)
: state(state) {}
DNSServer::DNSServer(std::shared_ptr<ServerState> state) : state(state) {}
void DNSServer::listen(const std::string& addr, int port) {
if (port == 0) {
@@ -62,11 +61,11 @@ asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::
if (bytes < 0x0C) {
dns_server_log.warning_f("input query too small");
phosg::print_data(stderr, input.data(), bytes);
} else if (!this->state->banned_ipv4_ranges->check(sender_addr)) {
} else if (!this->state->data->banned_ipv4_ranges->check(sender_addr)) {
input.resize(bytes);
uint32_t connect_address = is_local_address(sender_addr)
? this->state->local_address
: this->state->external_address;
? this->state->data->local_address
: this->state->data->external_address;
std::string response = this->response_for_query(input, connect_address);
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
}
+1 -1
View File
@@ -8,7 +8,7 @@
#include "IPV4RangeSet.hh"
struct ServerState;
class ServerState;
class DNSServer {
public:
+2063
View File
File diff suppressed because it is too large Load Diff
+421
View File
@@ -0,0 +1,421 @@
#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::unordered_map<uint16_t, uint16_t> ip_stack_port_remap;
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;
uint64_t psopeeps_dcv2_exp_multiplier = 5;
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;
bool allow_same_account_concurrent_logins_across_client_sources = false;
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 enable_bb_ship_selection_menu = false;
bool enable_brutal_peeps_mode = false;
bool enable_hardcore_mode = false;
bool enable_test_mode = 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;
int64_t dc_v2_exp_multiplier = 1;
int64_t gc_v3_exp_multiplier = 1;
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();
};
+2 -2
View File
@@ -811,7 +811,7 @@ void DownloadSession::on_request_complete() {
}
}
const std::vector<DownloadSession::GameConfig> DownloadSession::game_configs({
const std::vector<DownloadSession::GameConfig> DownloadSession::game_configs{
{.mode = GameMode::NORMAL, .episode = Episode::EP1, .v1 = true, .v2 = true, .v3 = true},
{.mode = GameMode::NORMAL, .episode = Episode::EP2, .v1 = false, .v2 = false, .v3 = true},
{.mode = GameMode::NORMAL, .episode = Episode::EP4, .v1 = false, .v2 = false, .v3 = false},
@@ -821,4 +821,4 @@ const std::vector<DownloadSession::GameConfig> DownloadSession::game_configs({
{.mode = GameMode::SOLO, .episode = Episode::EP1, .v1 = false, .v2 = false, .v3 = false},
{.mode = GameMode::SOLO, .episode = Episode::EP2, .v1 = false, .v2 = false, .v3 = false},
{.mode = GameMode::SOLO, .episode = Episode::EP4, .v1 = false, .v2 = false, .v3 = false},
});
};
+2 -2
View File
@@ -1738,7 +1738,7 @@ bool Server::update_registration_phase() {
return true;
}
const std::unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
const std::unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers{
{0x0B, &Server::handle_CAx0B_redraw_initial_hand},
{0x0C, &Server::handle_CAx0C_end_redraw_initial_hand_phase},
{0x0D, &Server::handle_CAx0D_end_non_action_phase},
@@ -1762,7 +1762,7 @@ const std::unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers
{0x41, &Server::handle_CAx41_map_request},
{0x48, &Server::handle_CAx48_end_turn},
{0x49, &Server::handle_CAx49_card_counts},
});
};
void Server::on_server_data_input(std::shared_ptr<Client> sender_c, const std::string& data) {
auto header = check_size_t<G_CardBattleCommandHeader>(data, 0xFFFF);
+1 -1
View File
@@ -13,7 +13,7 @@
struct Lobby;
class Client;
struct ServerState;
class ServerState;
namespace Episode3 {
+2 -2
View File
@@ -153,7 +153,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();
}
@@ -168,7 +168,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);
c->listener_port = listen_sock->endpoint.port();
+28 -24
View File
@@ -98,7 +98,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
};
auto require_account_admin_secret = [this](const HTTPRequest& req) -> void {
const auto& account_sync_config = this->state->config_json->get("AccountSync", phosg::JSON::dict());
const auto& account_sync_config = this->state->data->config_json->get("AccountSync", phosg::JSON::dict());
std::string shared_secret = account_sync_config.get_string("SharedSecret", "");
if (shared_secret.empty()) {
throw HTTPError(403, "Account admin mutations are disabled");
@@ -231,7 +231,7 @@ HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
}
}
std::string server_name = escape_label(this->state->name);
std::string server_name = escape_label(this->state->data->name);
std::string revision = escape_label(GIT_REVISION_HASH);
std::string out;
@@ -296,7 +296,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()) {
@@ -540,7 +540,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++) {
@@ -1033,17 +1033,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},
});
};
@@ -1052,7 +1052,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 {
@@ -1118,14 +1118,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());
@@ -1137,7 +1141,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) {
@@ -1158,7 +1162,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&) {
@@ -1170,7 +1174,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)};
@@ -1182,7 +1186,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;
@@ -1190,7 +1194,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());
});
@@ -1201,7 +1205,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;
@@ -1210,16 +1214,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));
@@ -1231,13 +1235,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");
}
+9 -9
View File
@@ -145,7 +145,7 @@ void IPSSClient::reschedule_idle_timeout() {
throw std::runtime_error("cannot reschedule idle timeout when simulator is missing");
}
this->idle_timeout_timer.cancel();
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->client_idle_timeout_usecs));
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->data->client_idle_timeout_usecs));
this->idle_timeout_timer.async_wait([this, sim](std::error_code ec) {
if (!ec) {
sim->log.info_f("Idle timeout expired on N-{:X}", this->network_id);
@@ -1359,8 +1359,8 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
// Figure out which logical port the connection should go to
uint16_t effective_server_port = conn->server_port;
auto remap_it = this->state->ip_stack_port_remap.find(effective_server_port);
if (remap_it != this->state->ip_stack_port_remap.end()) {
auto remap_it = this->state->data->ip_stack_port_remap.find(effective_server_port);
if (remap_it != this->state->data->ip_stack_port_remap.end()) {
this->log.info_f(
"Remapping IP stack TCP destination port {} to {} for connection {}",
effective_server_port,
@@ -1369,8 +1369,8 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
effective_server_port = remap_it->second;
}
auto port_config_it = this->state->number_to_port_config.find(effective_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(effective_server_port);
if (port_config_it == this->state->data->number_to_port_config.end()) {
this->log.error_f(
"TCP connection {} is to undefined port {} after remap from {}",
conn_str,
@@ -1385,20 +1385,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, effective_server_port, port_config->behavior);
this->state->game_server->connect_channel(conn->server_channel, effective_server_port, port_config.behavior);
this->log.info_f("Connected TCP connection {} to game server", conn_str);
}
}
@@ -1418,7 +1418,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();
}
+39 -10
View File
@@ -158,26 +158,42 @@ std::string IntegralExpression::UnaryOperatorNode::str() const {
}
}
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index) : flag_index(flag_index) {}
bool IntegralExpression::SectionIDLookupNode::operator==(const Node& other) const {
return (dynamic_cast<const SectionIDLookupNode*>(&other) != nullptr);
}
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
int64_t IntegralExpression::SectionIDLookupNode::evaluate(const Env& env) const {
return env.section_id;
}
std::string IntegralExpression::SectionIDLookupNode::str() const {
return "P_SID";
}
IntegralExpression::QuestFlagLookupNode::QuestFlagLookupNode(Difficulty difficulty, uint16_t flag_index)
: difficulty(difficulty), flag_index(flag_index) {}
bool IntegralExpression::QuestFlagLookupNode::operator==(const Node& other) const {
try {
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
return other_flag.flag_index == this->flag_index;
const QuestFlagLookupNode& other_flag = dynamic_cast<const QuestFlagLookupNode&>(other);
return ((other_flag.difficulty == this->difficulty) && (other_flag.flag_index == this->flag_index));
} catch (const std::bad_cast&) {
return false;
}
}
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::QuestFlagLookupNode::evaluate(const Env& env) const {
if (!env.flags) {
throw std::runtime_error("quest flags not available");
}
return env.flags->get(this->flag_index) ? 1 : 0;
Difficulty effective_difficulty = (this->difficulty == Difficulty::UNKNOWN) ? env.difficulty : this->difficulty;
return env.flags->get(effective_difficulty, this->flag_index) ? 1 : 0;
}
std::string IntegralExpression::FlagLookupNode::str() const {
return std::format("F_{:04X}", this->flag_index);
std::string IntegralExpression::QuestFlagLookupNode::str() const {
return (this->difficulty == Difficulty::UNKNOWN)
? std::format("F_{:04X}", this->flag_index)
: std::format("F_{:c}_{:04X}", abbreviation_for_difficulty(this->difficulty), this->flag_index);
}
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(Episode episode, uint8_t stage_index)
@@ -380,16 +396,29 @@ std::unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(s
}
// Check for env lookups
if (text == "P_SID") {
return std::make_unique<SectionIDLookupNode>();
}
if (text.starts_with("F_")) {
Difficulty difficulty = Difficulty::UNKNOWN;
if (text.starts_with("F_N_")) {
difficulty = Difficulty::NORMAL;
} else if (text.starts_with("F_H_")) {
difficulty = Difficulty::HARD;
} else if (text.starts_with("F_V_")) {
difficulty = Difficulty::VERY_HARD;
} else if (text.starts_with("F_U_")) {
difficulty = Difficulty::ULTIMATE;
}
char* endptr = nullptr;
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
uint64_t flag = strtoul(text.data() + ((difficulty == Difficulty::UNKNOWN) ? 2 : 4), &endptr, 16);
if (endptr != text.data() + text.size()) {
throw std::runtime_error("invalid flag lookup token");
}
if (flag >= 0x400) {
throw std::runtime_error("invalid flag index");
}
return std::make_unique<FlagLookupNode>(flag);
return std::make_unique<QuestFlagLookupNode>(difficulty, flag);
}
if (text.starts_with("CC_")) {
Episode episode;
+16 -4
View File
@@ -15,7 +15,9 @@
class IntegralExpression {
public:
struct Env {
const QuestFlagsForDifficulty* flags;
uint8_t section_id;
Difficulty difficulty;
const QuestFlags* flags;
const PlayerRecordsChallengeBB* challenge_records;
std::shared_ptr<const TeamIndex::Team> team;
size_t num_players;
@@ -105,15 +107,25 @@ protected:
std::unique_ptr<const Node> sub;
};
class FlagLookupNode : public Node {
class SectionIDLookupNode : public Node {
public:
FlagLookupNode(uint16_t flag_index);
virtual ~FlagLookupNode() = default;
SectionIDLookupNode() = default;
virtual ~SectionIDLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
};
class QuestFlagLookupNode : public Node {
public:
QuestFlagLookupNode(Difficulty difficulty, uint16_t flag_index);
virtual ~QuestFlagLookupNode() = default;
virtual bool operator==(const Node& other) const;
virtual int64_t evaluate(const Env& env) const;
virtual std::string str() const;
protected:
Difficulty difficulty;
uint16_t flag_index;
};
+4 -4
View File
@@ -6,10 +6,10 @@
#include "ItemParameterTable.hh"
#include "StaticGameData.hh"
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE({10});
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2({10, 10, 1, 10, 10, 10, 10, 10, 10, 1});
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4(
{10, 10, 1, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 99, 1});
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE{10};
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2{10, 10, 1, 10, 10, 10, 10, 10, 10, 1};
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4{
10, 10, 1, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 99, 1};
const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE(
Version::DC_NTE, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE, 999999);
+11 -11
View File
@@ -21,7 +21,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
// Nothing to do (it should be deleted)
} else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.sh.char_class, item.data.data1[4]);
if (item.data.data1[2] > max_level) {
throw std::runtime_error("technique level too high");
@@ -35,7 +35,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
auto& weapon = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::WEAPON)];
// Only enforce grind limits on BB, since the server doesn't have direct control over inventories on other versions
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
if (is_v4 && (weapon.data.data1[3] >= weapon_def.max_grind)) {
throw std::runtime_error("weapon already at maximum grind");
@@ -100,9 +100,9 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
}
armor.data.data1[5]++;
} else if (item.data.is_wrapped(*s->item_stack_limits(c->version()))) {
} else if (item.data.is_wrapped(*s->data->item_stack_limits(c->version()))) {
// Unwrap present
item.data.unwrap(*s->item_stack_limits(c->version()));
item.data.unwrap(*s->data->item_stack_limits(c->version()));
should_delete_item = false;
} else if (primary_identifier == 0x00330000) {
@@ -131,7 +131,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
} else if ((primary_identifier & 0xFFFF0000) == 0x030C0000) { // Non-combo mag cells
auto& mag = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::MAG)];
uint8_t evolution_number = s->mag_metadata_table(c->version())->get_evolution_number(mag.data.data1[1]);
uint8_t evolution_number = s->data->mag_metadata_table(c->version())->get_evolution_number(mag.data.data1[1]);
if (evolution_number < 4) {
switch (item.data.data1[2]) {
case 0x00: // Cell of MAG 502
@@ -159,7 +159,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
} else if ((primary_identifier & 0xFFFF0000) == 0x03150000) {
// Christmas Present, etc. - use unwrap_table + probabilities therein
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
auto table = item_parameter_table->get_event_items(item.data.data1[2]);
size_t sum = 0;
for (size_t z = 0; z < table.second; z++) {
@@ -198,7 +198,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
// The unopened Hunters Report's rank is stored in the kill count field; using the unopened report copies the rank
// to data1[2] and replaces the inventory item with a new item with the same ID. The game also moves the item to
// the end of the inventory, so we do the same.
const auto& stack_limits = *s->item_stack_limits(c->version());
const auto& stack_limits = *s->data->item_stack_limits(c->version());
auto report_item = player->remove_item(item.data.id, 1, stack_limits);
report_item.data1[2] = report_item.get_kill_count();
player->add_item(report_item, stack_limits);
@@ -213,7 +213,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
continue;
}
try {
auto item_parameter_table = s->item_parameter_table(c->version());
auto item_parameter_table = s->data->item_parameter_table(c->version());
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.sh.char_class) {
throw std::runtime_error("item combination requires specific char_class");
@@ -260,7 +260,7 @@ void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_p
if (should_delete_item) {
// Allow overdrafting meseta if the client is not BB, since the server isn't informed when meseta is added or
// removed from the bank.
player->remove_item(item.data.id, 1, *s->item_stack_limits(c->version()));
player->remove_item(item.data.id, 1, *s->data->item_stack_limits(c->version()));
}
}
@@ -488,8 +488,8 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
apply_mag_feed_result(
player->inventory.items[mag_item_index].data,
player->inventory.items[fed_item_index].data,
s->item_parameter_table(c->version()),
s->mag_metadata_table(c->version()),
s->data->item_parameter_table(c->version()),
s->data->mag_metadata_table(c->version()),
player->disp.visual.sh.char_class,
player->disp.visual.sh.section_id,
!is_v1_or_v2(c->version()));
+1 -1
View File
@@ -154,7 +154,7 @@ using LevelStatsDeltaBE = LevelStatsDeltaT<true>;
class LevelTable {
// This is the base class for all the LevelTable implementations. The public interface here only defines functions
// that the server needs to handle requests, but some subclasses implement more functionality. See the comments and
// Offsets structures inside the subclasses' constructor implementations for more details on the file formats.
// Root structures inside the subclasses' constructor implementations for more details on the file formats.
public:
virtual ~LevelTable() = default;
+14 -14
View File
@@ -173,7 +173,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);
}
@@ -216,14 +216,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,
@@ -287,7 +287,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);
}
@@ -317,13 +317,13 @@ void Lobby::create_ep3_server() {
bool is_nte = this->is_ep3_nte();
Episode3::Server::Options options = {
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
.map_index = s->ep3_map_index,
.behavior_flags = s->ep3_behavior_flags,
.card_index = is_nte ? s->data->ep3_card_index_trial : s->data->ep3_card_index,
.map_index = s->data->ep3_map_index,
.behavior_flags = s->data->ep3_behavior_flags,
.opt_rand_stream = nullptr,
.rand_crypt = this->rand_crypt,
.tournament = tourn,
.trap_card_ids = s->ep3_trap_card_ids,
.trap_card_ids = s->data->ep3_trap_card_ids,
.output_queue = nullptr,
};
if (is_nte) {
+1 -1
View File
@@ -20,7 +20,7 @@
#include "StaticGameData.hh"
#include "Text.hh"
struct ServerState;
class ServerState;
struct Lobby : public std::enable_shared_from_this<Lobby> {
struct FloorItem {
+407 -293
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -983,7 +983,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",
@@ -1792,7 +1792,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,
@@ -1986,8 +1986,8 @@ static asio::awaitable<HandlerResult> C_06(std::shared_ptr<Client> c, Channel::M
}
auto s = c->require_server_state();
char command_sentinel = s->chat_command_sentinel
? s->chat_command_sentinel
char command_sentinel = s->data->chat_command_sentinel
? s->data->chat_command_sentinel
: ((c->version() == Version::DC_11_2000) ? '@' : '$');
bool is_command = (text[0] == command_sentinel) ||
(text[0] == '\t' && text[1] != 'C' && text[2] == command_sentinel);
+8 -8
View File
@@ -25,14 +25,14 @@ void ProxySession::set_drop_mode(
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
this->item_creator = std::make_shared<ItemCreator>(
s->common_item_set(version, nullptr),
s->rare_item_set(version, nullptr),
s->armor_random_set,
s->tool_random_set,
s->weapon_random_set(this->lobby_difficulty),
s->tekker_adjustment_set,
s->item_parameter_table(version),
s->item_stack_limits(version),
s->data->common_item_set(version, nullptr),
s->data->rare_item_set(version, nullptr),
s->data->armor_random_set,
s->data->tool_random_set,
s->data->weapon_random_set(this->lobby_difficulty),
s->data->tekker_adjustment_set,
s->data->item_parameter_table(version),
s->data->item_stack_limits(version),
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
this->lobby_difficulty,
this->lobby_section_id,
+1 -1
View File
@@ -13,7 +13,7 @@
#include "Map.hh"
#include "SaveFileFormats.hh"
struct ServerState;
class ServerState;
struct ProxySession {
bool ending_intentionally = false;
+2 -2
View File
@@ -89,7 +89,7 @@ RareItemSet::ExpandedDrop RareItemSet::ParsedRELData::PackedDrop::expand() const
template <bool BE>
void RareItemSet::ParsedRELData::parse_t(phosg::StringReader r, bool is_v1) {
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
const auto& root = r.pget<OffsetsT<BE>>(footer.root_offset);
const auto& root = r.pget<RootT<BE>>(footer.root_offset);
phosg::StringReader monsters_r = r.sub(root.monster_rares_offset);
for (size_t z = 0; z < (is_v1 ? 0x33 : 0x65); z++) {
@@ -114,7 +114,7 @@ template <bool BE>
std::string RareItemSet::ParsedRELData::serialize_t(bool is_v1) const {
static const PackedDrop empty_drop;
OffsetsT<BE> root;
RootT<BE> root;
root.box_count = this->box_rares.size();
phosg::StringWriter w;
+4 -4
View File
@@ -87,15 +87,15 @@ protected:
} __packed_ws__(PackedDrop, 4);
template <bool BE>
struct OffsetsT {
struct RootT {
/* 00 */ U32T<BE> monster_rares_offset; // -> parray<PackedDrop, 0x65> (or 0x33 on v1)
/* 04 */ U32T<BE> box_count; // Usually 30 (0x1E)
/* 08 */ U32T<BE> box_areas_offset; // -> parray<uint8_t, 0x1E>
/* 0C */ U32T<BE> box_rares_offset; // -> parray<PackedDrop, 0x1E>
/* 10 */
} __packed_ws_be__(OffsetsT, 0x10);
using Offsets = OffsetsT<false>;
using OffsetsBE = OffsetsT<true>;
} __packed_ws_be__(RootT, 0x10);
using Root = RootT<false>;
using RootBE = RootT<true>;
struct BoxRare {
uint8_t area_norm_plus_1;
+175 -166
View File
File diff suppressed because it is too large Load Diff
+111 -115
View File
@@ -212,7 +212,7 @@ static std::string bb_hardcore_dead_filename_for_subcommands(std::shared_ptr<Cli
static bool current_ship_is_hardcore_bb(std::shared_ptr<Client> c) {
try {
auto s = c->require_server_state();
return s->enable_hardcore_mode && (c->version() == Version::BB_V4);
return s->data->enable_hardcore_mode && (c->version() == Version::BB_V4);
} catch (const std::exception&) {
return false;
}
@@ -514,7 +514,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");
@@ -1118,7 +1118,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();
@@ -1131,7 +1131,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;
}
@@ -1148,7 +1148,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();
@@ -1161,7 +1161,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;
}
@@ -1171,7 +1171,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();
@@ -1180,7 +1180,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;
}
@@ -1191,7 +1191,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)) {
@@ -1204,7 +1204,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;
}
@@ -1214,7 +1214,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;
@@ -1223,7 +1223,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;
@@ -1239,7 +1239,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;
@@ -1248,7 +1248,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;
@@ -1570,7 +1570,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);
@@ -1712,12 +1713,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);
}
@@ -2154,12 +2155,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();
@@ -2194,15 +2195,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();
}
@@ -2243,7 +2244,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();
@@ -2277,7 +2278,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
@@ -2295,7 +2296,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();
@@ -2321,13 +2322,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();
@@ -2346,7 +2347,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:
@@ -2358,7 +2359,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);
}
@@ -2382,7 +2383,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;
@@ -2400,15 +2401,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) {
@@ -2474,7 +2469,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",
@@ -2485,7 +2480,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();
}
@@ -2506,20 +2501,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({
@@ -2612,7 +2607,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);
@@ -2641,9 +2636,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);
@@ -2651,7 +2646,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)) {
@@ -2751,7 +2746,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);
@@ -2814,7 +2809,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)";
@@ -2886,7 +2881,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
@@ -2898,7 +2893,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();
@@ -2921,14 +2916,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();
@@ -3212,7 +3207,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) {
@@ -3250,7 +3245,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 {}",
@@ -3630,7 +3625,7 @@ static asio::awaitable<void> dispatch_dc_v2_exp_patch(std::shared_ptr<Client> c)
key += diff_str;
try {
auto base_fn = server_state->client_functions->get(key, c->specific_version);
auto base_fn = server_state->data->client_functions->get(key, c->specific_version);
auto fn = std::make_shared<ClientFunctionIndex::Function>(*base_fn);
for (size_t z = 0; z < 213; z++) {
@@ -3642,7 +3637,7 @@ static asio::awaitable<void> dispatch_dc_v2_exp_patch(std::shared_ptr<Client> c)
uint16_t base_exp = static_cast<uint8_t>(fn->code[offset]) |
(static_cast<uint16_t>(static_cast<uint8_t>(fn->code[offset + 1])) << 8);
uint64_t scaled_exp = base_exp * static_cast<uint64_t>(server_state->dc_v2_exp_multiplier);
uint64_t scaled_exp = base_exp * static_cast<uint64_t>(server_state->data->dc_v2_exp_multiplier);
if (scaled_exp > 0xFFFF) {
scaled_exp = 0xFFFF;
}
@@ -3696,7 +3691,7 @@ static asio::awaitable<void> dispatch_gc_v3_exp_patch(std::shared_ptr<Client> c)
try {
auto server_state = c->require_server_state();
auto base_fn = server_state->client_functions->get(key, c->specific_version);
auto base_fn = server_state->data->client_functions->get(key, c->specific_version);
auto fn = std::make_shared<ClientFunctionIndex::Function>(*base_fn);
for (size_t z = 0; z < num_exp_labels; z++) {
@@ -3713,7 +3708,7 @@ static asio::awaitable<void> dispatch_gc_v3_exp_patch(std::shared_ptr<Client> c)
static_cast<uint32_t>(static_cast<uint8_t>(fn->code[offset + 3]));
uint64_t scaled_exp = static_cast<uint64_t>(base_exp) *
static_cast<uint64_t>(server_state->gc_v3_exp_multiplier);
static_cast<uint64_t>(server_state->data->gc_v3_exp_multiplier);
if (scaled_exp > 0xFFFFFFFFULL) {
scaled_exp = 0xFFFFFFFFULL;
}
@@ -4280,7 +4275,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;
}
@@ -4320,7 +4315,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;
@@ -4356,7 +4351,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);
@@ -4461,7 +4456,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)) ||
@@ -4480,7 +4475,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.
@@ -4532,7 +4527,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);
@@ -4544,7 +4539,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,
@@ -4638,7 +4633,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);
}
}
@@ -4674,7 +4669,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;
@@ -4695,7 +4690,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();
@@ -4703,7 +4698,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);
}
@@ -4726,11 +4721,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();
@@ -4793,11 +4788,11 @@ static void on_exchange_item_for_team_points_bb(std::shared_ptr<Client> c, Subco
}
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);
uint32_t earned_points = points * amount;
if (TeamSync::relay_team_points_enabled() &&
!TeamSync::enqueue_team_member_update(c->login->account->account_id, "", earned_points)) {
@@ -4812,7 +4807,7 @@ static void on_exchange_item_for_team_points_bb(std::shared_ptr<Client> c, Subco
s->team_index->add_member_points(c->login->account->account_id, earned_points);
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, earned_points);
c->print_inventory();
@@ -4840,10 +4835,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();
@@ -4892,7 +4887,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
@@ -4976,7 +4971,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();
}
@@ -4995,12 +4990,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();
@@ -5020,7 +5015,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);
@@ -5042,7 +5037,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();
}
@@ -5096,7 +5091,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();
@@ -5134,7 +5129,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);
@@ -5169,7 +5164,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) {
@@ -5240,7 +5235,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);
}
@@ -5409,7 +5404,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);
@@ -5444,10 +5439,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);
}
@@ -5465,7 +5460,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);
@@ -5498,7 +5493,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();
@@ -5593,7 +5588,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;
@@ -5626,7 +5621,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
@@ -5650,7 +5645,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");
}
@@ -5665,7 +5660,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);
@@ -5695,12 +5690,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;
@@ -5760,7 +5755,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);
@@ -5773,7 +5768,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()) {
@@ -5784,7 +5779,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;
}
@@ -5798,7 +5793,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
@@ -5811,7 +5806,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) {
@@ -5838,7 +5833,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);
@@ -5900,7 +5895,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");
}
@@ -5930,8 +5925,9 @@ static void on_upgrade_weapon_attribute_bb(std::shared_ptr<Client> c, Subcommand
throw std::runtime_error("bonus value exceeds 100");
}
p->remove_item(payment_item.id, cmd.payment_count, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, payment_item.id, cmd.payment_count);
auto removed_payment_item = p->remove_item(
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;
item.data1[attribute_index + 1] = new_attr_value;
+58 -49
View File
@@ -319,19 +319,17 @@ void ReplaySession::apply_default_mask(std::shared_ptr<Event> ev) {
break;
}
default:
throw std::logic_error("invalid game version");
throw std::logic_error("Invalid game version");
}
}
ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
: state(state),
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
commands_sent(0),
bytes_sent(0),
commands_received(0),
bytes_received(0),
idle_timeout_timer(*this->state->io_context),
run_failed(false) {
idle_timeout_timer(*this->state->io_context) {
std::shared_ptr<Event> parsing_command = nullptr;
size_t line_num = 0;
@@ -364,16 +362,16 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
}
if (line == "### use psov2 crypt") {
this->state->use_psov2_rand_crypt = true;
this->use_psov2_rand_crypt = true;
}
if (line == "### use legacy item random behavior") {
this->state->use_legacy_item_random_behavior = true;
this->use_legacy_item_random_behavior = true;
}
if (line.starts_with("### cc ")) {
// ### cc $<chat command>
if (this->clients.size() != 1) {
throw std::runtime_error(std::format(
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
"(ev-line {}) Bare `cc` shortcut cannot be used with multiple clients connected; use `on C-X cc` instead",
line_num));
}
std::shared_ptr<Event> event;
@@ -383,7 +381,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
event->data = encode_chat_message(c->version, line.substr(7));
num_events++;
} catch (const std::exception& e) {
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
throw std::runtime_error(std::format("(ev-line {}) Failed to generate chat message ({})", line_num, e.what()));
}
continue;
@@ -400,7 +398,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
num_events++;
} catch (const std::exception& e) {
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
throw std::runtime_error(std::format("(ev-line {}) Failed to generate chat message ({})", line_num, e.what()));
}
continue;
@@ -412,21 +410,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
auto tokens = phosg::split(line, ' ');
if (!tokens[8].starts_with("C-")) {
throw std::runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client connection message missing client ID token", line_num));
}
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
auto listen_tokens = phosg::split(tokens[10], '-');
if (listen_tokens.size() < 4) {
throw std::runtime_error(std::format(
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
"(ev-line {}) Client connection message listening socket token format is incorrect", line_num));
}
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
if (!this->clients.emplace(c->id, c).second) {
throw std::runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
throw std::runtime_error(std::format("(ev-line {}) Duplicate client ID in input log", line_num));
}
this->create_event(Event::Type::CONNECT, c, line_num);
num_events++;
@@ -438,21 +436,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' ');
if (tokens.size() < 11) {
throw std::runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client disconnection message has incorrect token count", line_num));
}
if (!tokens[10].starts_with("C-")) {
throw std::runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client disconnection message missing client ID token", line_num));
}
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
try {
auto& c = this->clients.at(client_id);
if (c->disconnect_event.get()) {
throw std::runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
throw std::runtime_error(std::format("(ev-line {}) Client has multiple disconnect events", line_num));
}
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
num_events++;
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
throw std::runtime_error(std::format("(ev-line {}) Unknown disconnecting client ID in input log", line_num));
}
continue;
}
@@ -466,7 +464,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
if (offset != std::string::npos) {
auto tokens = phosg::split(line, ' ');
if (tokens.size() < 10) {
throw std::runtime_error(std::format("(ev-line {}) command header line too short", line_num));
throw std::runtime_error(std::format("(ev-line {}) Command header line too short", line_num));
}
bool from_client = (tokens[6] == "Received");
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
@@ -475,7 +473,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
num_events++;
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
throw std::runtime_error(std::format("(ev-line {}) Input log contains command for missing client", line_num));
}
continue;
}
@@ -494,8 +492,12 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
}
asio::awaitable<void> ReplaySession::run() {
bool prev_use_psov2_rand_crypt = this->state->use_psov2_rand_crypt;
bool prev_use_legacy_item_random_behavior = this->state->use_legacy_item_random_behavior;
this->state->use_psov2_rand_crypt = this->use_psov2_rand_crypt;
this->state->use_legacy_item_random_behavior = this->use_legacy_item_random_behavior;
try {
replay_log.info_f("Starting replay");
while (this->first_event) {
if (!this->first_event->complete) {
auto& c = this->clients.at(this->first_event->client_id);
@@ -505,15 +507,15 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::CONNECT: {
if (c->channel->connected()) {
throw std::runtime_error(std::format(
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
"(ev-line {}) Connect event on already-connected client", this->first_event->line_num));
}
std::shared_ptr<const PortConfiguration> port_config;
const DataIndex::PortConfiguration* port_config;
try {
port_config = this->state->number_to_port_config.at(c->port);
port_config = &this->state->data->number_to_port_config.at(c->port);
} catch (const std::out_of_range&) {
throw std::runtime_error(std::format(
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
"(ev-line {}) Client connected to port missing from configuration", this->first_event->line_num));
}
auto server_channel = std::make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
@@ -523,7 +525,7 @@ asio::awaitable<void> ReplaySession::run() {
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
} else {
throw std::runtime_error(std::format(
"(ev-line {}) no server available for connection", this->first_event->line_num));
"(ev-line {}) No server available for connection", this->first_event->line_num));
}
break;
}
@@ -535,7 +537,7 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::SEND:
if (!c->channel->connected()) {
throw std::runtime_error(std::format(
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
"(ev-line {}) Send event attempted on unconnected client", this->first_event->line_num));
}
c->channel->send(this->first_event->data);
this->commands_sent++;
@@ -544,8 +546,8 @@ asio::awaitable<void> ReplaySession::run() {
case Event::Type::RECEIVE: {
if (!c->channel->connected()) {
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client",
this->first_event->line_num));
throw std::runtime_error(std::format(
"(ev-line {}) Receive event on non-connected client", this->first_event->line_num));
}
if (c->receive_events.front() != this->first_event) {
throw std::logic_error("Client receive events are out of order");
@@ -561,25 +563,29 @@ asio::awaitable<void> ReplaySession::run() {
this->bytes_received += full_command.size();
if (c->receive_events.empty()) {
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error("received unexpected command for client");
std::string data_str = phosg::format_data(full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format("Received unexpected command for client:\n{}", data_str));
}
auto& ev = c->receive_events.front();
if ((full_command.size() != ev->data.size()) && !ev->allow_size_disparity) {
replay_log.error_f("Expected command:");
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:");
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
std::string expected_data = phosg::format_data(
ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
std::string received_data = phosg::format_data(
full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format(
"(ev-line {}) Received command sizes do not match:\nExpected command:\n{}\nReceived command:\n{}",
ev->line_num, expected_data, received_data));
}
for (size_t x = 0; x < std::min<size_t>(full_command.size(), ev->data.size()); x++) {
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
replay_log.error_f("Expected command:");
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
replay_log.error_f("Received command:");
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
std::string expected_data = phosg::format_data(
ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
std::string received_data = phosg::format_data(
full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
throw std::runtime_error(std::format(
"(ev-line {}) Received command does not match expected\nExpected command:\n{}\nReceived command:\n{}",
ev->line_num, expected_data, received_data));
}
}
@@ -624,18 +630,18 @@ asio::awaitable<void> ReplaySession::run() {
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
// first one here.
c->channel->crypt_in = std::make_shared<PSOBBEncryption>(
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
*this->state->data->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
c->channel->crypt_out = std::make_shared<PSOBBEncryption>(
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
*this->state->data->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
}
break;
default:
throw std::logic_error("unsupported encryption version");
throw std::logic_error("Unsupported encryption version");
}
break;
}
default:
throw std::logic_error("unhandled event type");
throw std::logic_error("Unhandled event type");
}
this->first_event->complete = true;
}
@@ -646,13 +652,12 @@ asio::awaitable<void> ReplaySession::run() {
}
}
} catch (const std::exception& e) {
replay_log.error_f("Replay failed: {}", e.what());
this->failure = std::format("Replay failed: {}", e.what());
if (this->first_event) {
replay_log.error_f("Next pending event: {}", this->first_event->str());
this->failure += std::format("\nNext pending event: {}", this->first_event->str());
} else {
replay_log.error_f("No events are pending at failure time");
this->failure += std::format("\nNo events are pending at failure time");
}
this->run_failed = true;
}
for (auto& [_, c] : this->clients) {
@@ -660,14 +665,18 @@ asio::awaitable<void> ReplaySession::run() {
c->channel->disconnect();
}
}
this->state->use_psov2_rand_crypt = this->prev_psov2_crypt_enabled;
if (!this->run_failed) {
this->state->use_psov2_rand_crypt = prev_use_psov2_rand_crypt;
this->state->use_legacy_item_random_behavior = prev_use_legacy_item_random_behavior;
if (this->failure.empty()) {
// Wait a bit longer to ensure that any command sent at the end of the replay session don't crash the server
co_await async_sleep(std::chrono::seconds(2));
replay_log.info_f("Replay complete: {} commands sent ({} bytes), {} commands received ({} bytes)",
this->commands_sent, this->bytes_sent, this->commands_received, this->bytes_received);
}
this->idle_timeout_timer.cancel();
}
void ReplaySession::reschedule_idle_timeout() {
+6 -4
View File
@@ -21,8 +21,9 @@ public:
~ReplaySession() = default;
asio::awaitable<void> run();
inline bool failed() const {
return this->run_failed;
inline std::string failure_str() const {
return this->failure;
}
private:
@@ -62,7 +63,8 @@ private:
};
std::shared_ptr<ServerState> state;
bool prev_psov2_crypt_enabled;
bool use_psov2_rand_crypt = false;
bool use_legacy_item_random_behavior = false;
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
@@ -75,7 +77,7 @@ private:
size_t bytes_received;
asio::steady_timer idle_timeout_timer;
bool run_failed;
std::string failure;
std::shared_ptr<ReplaySession::Event> create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num);
+76 -76
View File
@@ -35,7 +35,7 @@ inline uint8_t get_pre_v1_subcommand(Version v, uint8_t nte_subcommand, uint8_t
}
}
const std::unordered_set<uint32_t> v2_crypt_initial_client_commands({
const std::unordered_set<uint32_t> v2_crypt_initial_client_commands{
0x00260088, // (17) DCNTE license check
0x00B0008B, // (02) DCNTE login
0x00B0018B, // (02) DCNTE login (UDP off)
@@ -54,20 +54,20 @@ const std::unordered_set<uint32_t> v2_crypt_initial_client_commands({
0x0130019D, // (02) DCv2/GCNTE extended login (UDP off)
// Note: PSO PC initial commands are not listed here because we don't use a detector encryption for PSO PC
// (instead, we use the split reconnect command to send PC to a different port).
});
const std::unordered_set<uint32_t> v3_crypt_initial_client_commands({
};
const std::unordered_set<uint32_t> v3_crypt_initial_client_commands{
0x00E000DB, // (17) GC/XB license check
0x00EC009E, // (02) GC login
0x00EC019E, // (02) GC login (UDP off)
0x0150009E, // (02) GC extended login
0x0150019E, // (02) GC extended login (UDP off)
});
};
const std::unordered_set<std::string> bb_crypt_initial_client_commands({
const std::unordered_set<std::string> bb_crypt_initial_client_commands{
std::string("\xB4\x00\x93\x00\x00\x00\x00\x00", 8),
std::string("\xAC\x00\x93\x00\x00\x00\x00\x00", 8),
std::string("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8),
});
};
void send_command(
std::shared_ptr<Client> c,
@@ -234,7 +234,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));
@@ -338,7 +338,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 {
@@ -347,7 +347,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);
}
@@ -362,7 +362,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);
}
@@ -519,7 +519,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;
@@ -528,7 +528,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;
@@ -552,7 +552,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
@@ -564,7 +564,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());
@@ -575,7 +575,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
@@ -720,10 +720,10 @@ static std::string bb_stream_file_data_for_client(std::shared_ptr<Client> c) {
const auto* brutal_peeps_def = brutal_peeps_tier_definition(effective_brutal_peeps_hp_scale_tier);
if (!brutal_peeps_def) {
return s->bb_stream_file->data;
return s->data->bb_stream_file->data;
}
std::string scaled_data = s->bb_stream_file->data;
std::string scaled_data = s->data->bb_stream_file->data;
double mult = brutal_peeps_def->enemy_hp_multiplier;
size_t ultimate_index = static_cast<size_t>(Difficulty::ULTIMATE);
@@ -741,7 +741,7 @@ static std::string bb_stream_file_data_for_client(std::shared_ptr<Client> c) {
return static_cast<uint16_t>(scaled);
};
for (const auto& sf_entry : s->bb_stream_file->entries) {
for (const auto& sf_entry : s->data->bb_stream_file->entries) {
if (!is_battle_param_stream_file_for_brutal_peeps(sf_entry.filename)) {
continue;
}
@@ -893,12 +893,12 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
return static_cast<uint16_t>(scaled);
};
auto fn = s->client_functions->get("PsoPeepsBrutalPeepsHP", c->specific_version);
auto fn = s->data->client_functions->get("PsoPeepsBrutalPeepsHP", c->specific_version);
for (const auto& bp_filename : bp_filenames) {
const BBStreamFile::Entry* bp_entry = nullptr;
const std::decay_t<decltype(s->data->bb_stream_file->entries)>::value_type* bp_entry = nullptr;
for (const auto& sf_entry : s->bb_stream_file->entries) {
for (const auto& sf_entry : s->data->bb_stream_file->entries) {
if (sf_entry.filename == bp_filename) {
bp_entry = &sf_entry;
break;
@@ -909,13 +909,13 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
continue;
}
if ((bp_entry->offset > s->bb_stream_file->data.size()) ||
(bp_entry->size > (s->bb_stream_file->data.size() - bp_entry->offset))) {
if ((bp_entry->offset > s->data->bb_stream_file->data.size()) ||
(bp_entry->size > (s->data->bb_stream_file->data.size() - bp_entry->offset))) {
c->log.warning_f("Skipping Brutal Peeps HP client patch: invalid {} range", bp_filename);
continue;
}
const char* vanilla_data = s->bb_stream_file->data.data() + bp_entry->offset;
const char* vanilla_data = s->data->bb_stream_file->data.data() + bp_entry->offset;
if (bp_entry->size < signature_size) {
c->log.warning_f("Skipping Brutal Peeps HP client patch: {} too small for signature", bp_filename);
@@ -1220,7 +1220,7 @@ static std::vector<std::pair<std::string, std::shared_ptr<AsyncPromise<C_Execute
suffix[14] = static_cast<char>((patch_entry_count >> 16) & 0xFF);
suffix[15] = static_cast<char>((patch_entry_count >> 24) & 0xFF);
auto fn = s->client_functions->get("PsoPeepsBrutalPeepsPC", c->specific_version);
auto fn = s->data->client_functions->get("PsoPeepsBrutalPeepsPC", c->specific_version);
auto promise = std::make_shared<AsyncPromise<C_ExecuteCodeResult_B3>>();
c->function_call_response_queue.emplace_back(promise);
@@ -1317,11 +1317,11 @@ void send_stream_file_index_bb(std::shared_ptr<Client> c) {
std::string contents = bb_stream_file_data_for_client(c);
c->log.info_f("BB stream file index: sending {} entries from {} bytes",
s->bb_stream_file->entries.size(), contents.size());
s->data->bb_stream_file->entries.size(), contents.size());
std::vector<S_StreamFileIndexEntry_BB_01EB> entries;
size_t idx = 0;
for (const auto& sf_entry : s->bb_stream_file->entries) {
for (const auto& sf_entry : s->data->bb_stream_file->entries) {
bool offset_past_end = (sf_entry.offset > contents.size());
bool size_overflows = !offset_past_end && (sf_entry.size > (contents.size() - sf_entry.offset));
@@ -1827,17 +1827,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;
@@ -2111,7 +2111,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;
}
@@ -2279,7 +2279,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;
@@ -2302,7 +2302,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;
@@ -2325,7 +2325,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");
}
@@ -2529,7 +2529,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());
@@ -2543,7 +2543,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;
@@ -2573,7 +2573,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());
@@ -2612,7 +2612,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;
@@ -2722,11 +2722,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;
}
@@ -2836,13 +2836,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())) {
@@ -2909,10 +2909,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;
}
@@ -2957,13 +2957,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();
@@ -3115,7 +3115,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;
@@ -3142,7 +3142,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);
@@ -3423,7 +3423,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)++;
@@ -3448,7 +3448,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);
}
}
@@ -3736,7 +3736,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));
}
}
@@ -3749,7 +3749,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));
}
@@ -3959,8 +3959,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());
@@ -3982,8 +3982,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);
}
@@ -4039,7 +4039,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();
@@ -4346,7 +4346,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);
}
@@ -4407,14 +4407,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');
@@ -4444,7 +4444,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);
@@ -4479,7 +4479,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);
@@ -4546,7 +4546,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>
@@ -4736,33 +4736,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 {
+53 -2077
View File
File diff suppressed because it is too large Load Diff
+16 -381
View File
@@ -15,6 +15,7 @@
#include "CommonItemSet.hh"
#include "DNSServer.hh"
#include "DOLFileIndex.hh"
#include "DataIndex.hh"
#include "Episode3/DataIndexes.hh"
#include "Episode3/Tournament.hh"
#include "GSLArchive.hh"
@@ -37,301 +38,29 @@ 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::unordered_map<uint16_t, uint16_t> ip_stack_port_remap;
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;
uint64_t psopeeps_dcv2_exp_multiplier = 5;
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;
bool allow_same_account_concurrent_logins_across_client_sources = false;
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 enable_bb_ship_selection_menu = false;
bool use_psov2_rand_crypt = false; // Used in some tests
bool enable_brutal_peeps_mode = false;
bool enable_hardcore_mode = false;
bool enable_test_mode = false;
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;
int64_t dc_v2_exp_multiplier = 1;
int64_t gc_v3_exp_multiplier = 1;
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::unordered_map<uint64_t, std::shared_ptr<Client>> client_for_account_source;
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;
@@ -339,11 +68,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);
@@ -353,8 +79,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();
@@ -366,110 +91,20 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr, uint64_t account_id = 0, std::shared_ptr<Lobby> l = nullptr);
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
uint16_t game_server_port_for_version(Version v) const;
std::shared_ptr<const Menu> information_menu(Version version) const;
std::shared_ptr<const Menu> proxy_destinations_menu(Version version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations(Version version) const;
std::shared_ptr<const SetDataTableBase> set_data_table(Version version, Episode episode, GameMode mode, Difficulty difficulty) const;
inline std::shared_ptr<const WeaponShopRandomSet> weapon_random_set(Difficulty difficulty) const {
return this->weapon_random_sets.at(static_cast<size_t>(difficulty));
}
inline std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates(Difficulty difficulty) const {
return this->rare_enemy_rates_by_difficulty.at(static_cast<size_t>(difficulty));
}
std::shared_ptr<const LevelTable> level_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table(Version version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_encode(Version version) const;
std::shared_ptr<const MagMetadataTable> mag_metadata_table(Version version) const;
std::shared_ptr<const ItemData::StackLimits> item_stack_limits(Version version) const;
std::shared_ptr<const ItemNameIndex> item_name_index_opt(Version version) const; // Returns null if missing
std::shared_ptr<const ItemNameIndex> item_name_index(Version version) const; // Throws if missing
std::string describe_item(Version version, const ItemData& item, uint8_t flags = 0) const;
ItemData parse_item_description(Version version, const std::string& description) const;
std::shared_ptr<const CommonItemSet> common_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
std::shared_ptr<const RareItemSet> rare_item_set(Version logic_version, std::shared_ptr<const Quest> q) const;
const std::vector<uint32_t>& public_lobby_search_order(Version version, bool is_client_customization) const;
inline const std::vector<uint32_t>& public_lobby_search_order(std::shared_ptr<const Client> c) const {
return this->public_lobby_search_order(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
inline uint32_t name_color_for_client(Version v, bool is_client_customization) const {
if (is_client_customization && this->client_customization_name_color) {
return this->client_customization_name_color;
}
return this->version_name_colors ? this->version_name_colors->at(static_cast<size_t>(v) - NUM_PATCH_VERSIONS) : 0;
}
inline uint32_t name_color_for_client(std::shared_ptr<const Client> c) const {
return this->name_color_for_client(c->version(), c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION));
}
std::shared_ptr<const std::vector<std::string>> information_contents_for_client(std::shared_ptr<const Client> c) const;
size_t default_min_level_for_game(Version version, Episode episode, Difficulty difficulty) const;
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file(const std::string& patch_index_filename) const;
std::shared_ptr<const std::string> load_map_file(Version version, const std::string& filename) const;
std::shared_ptr<const std::string> load_map_file_uncached(Version version, const std::string& filename) const;
std::pair<std::string, uint16_t> parse_port_spec(const phosg::JSON& json) const;
std::vector<PortConfiguration> parse_port_configuration(const phosg::JSON& json) const;
static constexpr uint32_t free_play_key(
Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities) {
return (static_cast<uint32_t>(episode) << 28) |
(static_cast<uint32_t>(mode) << 26) |
(static_cast<uint32_t>(difficulty) << 24) |
(static_cast<uint32_t>(floor) << 16) |
(static_cast<uint32_t>(layout) << 8) |
(static_cast<uint32_t>(entities) << 0);
}
std::shared_ptr<const SuperMap> get_free_play_supermap(
Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities);
std::vector<std::shared_ptr<const SuperMap>> supermaps_for_variations(
Episode episode, GameMode mode, Difficulty difficulty, const Variations& variations);
void create_default_lobbies();
void collect_network_addresses();
void load_config_early();
void load_config_late();
void load_bb_private_keys();
void load_bb_system_defaults();
void load_accounts();
void load_teams();
void load_patch_indexes();
void load_maps();
void load_battle_params();
void load_level_tables();
void load_text_index();
std::shared_ptr<ItemNameIndex> create_item_name_index_for_version(
std::shared_ptr<const ItemParameterTable> pmt,
std::shared_ptr<const ItemData::StackLimits> limits,
std::shared_ptr<const TextIndex> text_index) const;
void load_item_name_indexes();
void load_drop_tables();
void load_item_definitions();
void load_set_data_tables();
void load_word_select_table();
void load_ep3_cards();
void load_ep3_maps(bool raise_on_any_failure = false);
void load_ep3_tournament_state();
void load_quest_index(bool raise_on_any_failure = false);
void compile_functions(bool raise_on_any_failure = false);
void load_dol_files();
void generate_bb_stream_file();
void load_all(bool enable_thread_pool);
void update_default_lobby_events_from_config();
void reset_between_replays();
void disconnect_all_banned_clients();
protected:
ServerState() = default;
ServerState(const ServerState&) = delete;
ServerState(ServerState&&) = delete;
ServerState& operator=(const ServerState&) = delete;
ServerState& operator=(ServerState&&) = delete;
};
+31 -26
View File
@@ -185,51 +185,55 @@ ShellCommand c_reload(
auto types = phosg::split(args.args, ' ');
for (const auto& type : types) {
if (type == "all") {
args.s->load_all(true);
args.s->data->load_all();
args.s->disconnect_all_banned_clients();
args.s->update_default_lobby_events_from_config();
} else if (type == "bb-keys") {
args.s->load_bb_private_keys();
args.s->data->load_bb_private_keys();
} else if (type == "accounts") {
args.s->load_accounts();
} else if (type == "maps") {
args.s->load_maps();
args.s->data->load_maps();
} else if (type == "patch-files") {
args.s->load_patch_indexes();
args.s->data->load_patch_indexes();
} else if (type == "ep3-cards") {
args.s->load_ep3_cards();
args.s->data->load_ep3_cards();
} else if (type == "ep3-maps") {
args.s->load_ep3_maps();
args.s->data->load_ep3_maps();
} else if (type == "ep3-tournaments") {
args.s->load_ep3_tournament_state();
} else if (type == "functions") {
args.s->compile_functions();
args.s->data->compile_functions();
} else if (type == "dol-files") {
args.s->load_dol_files();
args.s->data->load_dol_files();
} else if (type == "set-tables") {
args.s->load_set_data_tables();
args.s->data->load_set_data_tables();
} else if (type == "battle-params") {
args.s->load_battle_params();
args.s->generate_bb_stream_file();
args.s->data->load_battle_params();
args.s->data->generate_bb_stream_file();
} else if (type == "level-tables") {
args.s->load_level_tables();
args.s->generate_bb_stream_file();
args.s->data->load_level_tables();
args.s->data->generate_bb_stream_file();
} else if (type == "text-index") {
args.s->load_text_index();
args.s->data->load_text_index();
} else if (type == "word-select") {
args.s->load_word_select_table();
args.s->data->load_word_select_table();
} else if (type == "item-definitions") {
args.s->load_item_definitions();
args.s->generate_bb_stream_file();
args.s->data->load_item_definitions();
args.s->data->generate_bb_stream_file();
} else if (type == "item-name-index") {
args.s->load_item_name_indexes();
args.s->data->load_item_name_indexes();
} else if (type == "drop-tables") {
args.s->load_drop_tables();
args.s->data->load_drop_tables();
} else if (type == "config") {
args.s->load_config_early();
args.s->load_config_late();
args.s->data->load_config_early();
args.s->data->load_config_late();
args.s->disconnect_all_banned_clients();
args.s->update_default_lobby_events_from_config();
} else if (type == "teams") {
args.s->load_teams();
} else if (type == "quests") {
args.s->load_quest_index();
args.s->data->load_quest_index();
} else {
throw std::runtime_error("invalid data type: " + type);
}
@@ -663,7 +667,7 @@ ShellCommand c_announce_mail(
"announce-mail", "announce-mail MESSAGE\n\
Send an announcement message via Simple Mail to all players.",
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
send_simple_mail(args.s, 0, args.s->name, args.args);
send_simple_mail(args.s, 0, args.s->data->name, args.args);
co_return std::deque<std::string>{};
});
@@ -698,7 +702,7 @@ ShellCommand c_create_tournament(
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
std::string name = get_quoted_string(args.args);
std::string map_name = get_quoted_string(args.args);
auto map = args.s->ep3_map_index->map_for_name(map_name);
auto map = args.s->data->ep3_map_index->map_for_name(map_name);
uint32_t num_teams = std::stoul(get_quoted_string(args.args), nullptr, 0);
Episode3::Rules rules;
rules.set_defaults();
@@ -1089,13 +1093,14 @@ ShellCommand c_create_item(
throw std::runtime_error("proxy session is not game leader");
}
ItemData item = args.s->parse_item_description(c->version(), args.args);
ItemData item = args.s->data->parse_item_description(c->version(), args.args);
item.id = phosg::random_object<uint32_t>() | 0x80000000;
send_drop_stacked_item_to_channel(args.s, c->channel, item, c->floor, c->pos);
send_drop_stacked_item_to_channel(args.s, c->proxy_session->server_channel, item, c->floor, c->pos);
std::string name = args.s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
std::string name = args.s->data->describe_item(
c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
send_text_message(c->channel, "$C7Item created:\n" + name);
co_return std::deque<std::string>{};
});
+6 -6
View File
@@ -515,7 +515,7 @@ struct WeaponRootT {
U32T<BE> favored_grind_range_table; // {u32 min, u32 max}[6]
} __packed_ws_be__(WeaponRootT, 0x20);
const std::array<std::pair<uint8_t, uint8_t>, 0x48> WeaponShopRandomSet::type_defs({
const std::array<std::pair<uint8_t, uint8_t>, 0x48> WeaponShopRandomSet::type_defs{{
/* 00 */ {0x01, 0x00}, // Saber
/* 01 */ {0x01, 0x01}, // Brand
/* 02 */ {0x01, 0x02}, // Buster
@@ -588,9 +588,9 @@ const std::array<std::pair<uint8_t, uint8_t>, 0x48> WeaponShopRandomSet::type_de
/* 45 */ {0x0A, 0x05}, // MACE OF ADAMAN
/* 46 */ {0x0C, 0x05}, // ICE STAFF:DAGON
/* 47 */ {0x0B, 0x05}, // BRAVE HAMMER
});
}};
const std::array<std::pair<uint8_t, uint8_t>, 10> WeaponShopRandomSet::type_defs_39({
const std::array<std::pair<uint8_t, uint8_t>, 10> WeaponShopRandomSet::type_defs_39{{
// Indexed by section_id
{0x28, 0x00}, // HARISEN BATTLE FAN
{0x2A, 0x00}, // AKIKO'S WOK
@@ -602,9 +602,9 @@ const std::array<std::pair<uint8_t, uint8_t>, 10> WeaponShopRandomSet::type_defs
{0x59, 0x00}, // BROOM
{0x8A, 0x00}, // SANGE
{0x99, 0x00}, // ANGEL HARP
});
}};
const std::array<std::pair<uint8_t, uint8_t>, 10> WeaponShopRandomSet::type_defs_3A({
const std::array<std::pair<uint8_t, uint8_t>, 10> WeaponShopRandomSet::type_defs_3A{{
// Indexed by section_id
{0x99, 0x00}, // ANGEL HARP
{0x64, 0x00}, // CHAMELEON SCYTHE
@@ -616,7 +616,7 @@ const std::array<std::pair<uint8_t, uint8_t>, 10> WeaponShopRandomSet::type_defs
{0x2A, 0x00}, // AKIKO'S WOK
{0x48, 0x00}, // SAMBA MARACAS
{0x35, 0x00}, // CRAZY TUNE
});
}};
const std::array<int8_t, 20> WeaponShopRandomSet::bonus_values{
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
+7 -3
View File
@@ -27,8 +27,10 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
case SIGUSR1:
this->log.info_f("Received SIGUSR1; reloading config.json");
try {
this->state->load_config_early();
this->state->load_config_late();
this->state->data->load_config_early();
this->state->data->load_config_late();
this->state->disconnect_all_banned_clients();
this->state->update_default_lobby_events_from_config();
phosg::fwrite_fmt(stderr, "Configuration update complete\n");
} catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
@@ -38,7 +40,9 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
case SIGUSR2:
this->log.info_f("Received SIGUSR2; reloading config.json and all dependencies");
try {
this->state->load_all(true);
this->state->data->load_all();
this->state->disconnect_all_banned_clients();
this->state->update_default_lobby_events_from_config();
phosg::fwrite_fmt(stderr, "Configuration update complete\n");
} catch (const std::exception& e) {
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
+8 -8
View File
@@ -121,13 +121,13 @@ static const std::array<const char*, 10> section_id_to_name = {
static const std::array<const char*, 10> section_id_to_abbreviation = {
"Vir", "Grn", "Sky", "Blu", "Prp", "Pnk", "Red", "Orn", "Ylw", "Wht"};
const std::unordered_map<std::string, uint8_t> name_to_section_id({{"viridia", 0},
const std::unordered_map<std::string, uint8_t> name_to_section_id{
// Greennill is spelled Greenill in some places, so we accept both spellings
{"greennill", 1}, {"greenill", 1}, {"skyly", 2}, {"bluefull", 3}, {"purplenum", 4}, {"pinkal", 5}, {"redria", 6},
{"oran", 7}, {"yellowboze", 8}, {"whitill", 9},
{"viridia", 0}, {"greennill", 1}, {"greenill", 1}, {"skyly", 2}, {"bluefull", 3}, {"purplenum", 4}, {"pinkal", 5},
{"redria", 6}, {"oran", 7}, {"yellowboze", 8}, {"whitill", 9},
// Shortcuts for chat commands
{"b", 3}, {"g", 1}, {"o", 7}, {"pi", 5}, {"pu", 4}, {"r", 6}, {"s", 2}, {"v", 0}, {"w", 9}, {"y", 8}});
{"b", 3}, {"g", 1}, {"o", 7}, {"pi", 5}, {"pu", 4}, {"r", 6}, {"s", 2}, {"v", 0}, {"w", 9}, {"y", 8}};
const std::vector<std::string> lobby_event_to_name = {
"none", "xmas", "none", "val", "easter", "hallo", "sonic", "newyear",
@@ -673,10 +673,10 @@ char char_for_challenge_rank(uint8_t rank) {
return "BAS"[rank];
}
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V123({0, 19, 39, 79});
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP1({0, 19, 39, 79});
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP2({0, 29, 49, 89});
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP4({0, 39, 79, 109});
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V123{{0, 19, 39, 79}};
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP1{{0, 19, 39, 79}};
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP2{{0, 29, 49, 89}};
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP4{{0, 39, 79, 109}};
const std::array<GameMode, 2> ALL_GAME_MODES_V1 = {GameMode::NORMAL, GameMode::BATTLE};
const std::array<GameMode, 3> ALL_GAME_MODES_V23 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE};
+11 -10
View File
@@ -257,16 +257,17 @@ std::string TextTranscoderUTF8ToCustomSJIS::on_untranslatable(const void** src,
}
}
TextTranscoder tt_8859_to_utf8("UTF-8", "ISO-8859-1");
TextTranscoder tt_utf8_to_8859("ISO-8859-1", "UTF-8");
TextTranscoder tt_standard_sjis_to_utf8("UTF-8", "SHIFT_JIS");
TextTranscoder tt_utf8_to_standard_sjis("SHIFT_JIS", "UTF-8");
TextTranscoderCustomSJISToUTF8 tt_sega_sjis_to_utf8;
TextTranscoderUTF8ToCustomSJIS tt_utf8_to_sega_sjis;
TextTranscoder tt_utf16_to_utf8("UTF-8", "UTF-16LE");
TextTranscoder tt_utf8_to_utf16("UTF-16LE", "UTF-8");
TextTranscoder tt_ascii_to_utf8("UTF-8", "ASCII");
TextTranscoder tt_utf8_to_ascii("ASCII", "UTF-8");
// iconv_t is not thread-safe, so we need a separate one of these for each thread
thread_local TextTranscoder tt_8859_to_utf8("UTF-8", "ISO-8859-1");
thread_local TextTranscoder tt_utf8_to_8859("ISO-8859-1", "UTF-8");
thread_local TextTranscoder tt_standard_sjis_to_utf8("UTF-8", "SHIFT_JIS");
thread_local TextTranscoder tt_utf8_to_standard_sjis("SHIFT_JIS", "UTF-8");
thread_local TextTranscoderCustomSJISToUTF8 tt_sega_sjis_to_utf8;
thread_local TextTranscoderUTF8ToCustomSJIS tt_utf8_to_sega_sjis;
thread_local TextTranscoder tt_utf16_to_utf8("UTF-8", "UTF-16LE");
thread_local TextTranscoder tt_utf8_to_utf16("UTF-16LE", "UTF-8");
thread_local TextTranscoder tt_ascii_to_utf8("UTF-8", "ASCII");
thread_local TextTranscoder tt_utf8_to_ascii("ASCII", "UTF-8");
std::string tt_encode_marked_optional(const std::string& utf8, Language default_language, bool is_utf16) {
if (is_utf16) {
+10 -10
View File
@@ -77,16 +77,16 @@ protected:
virtual std::string on_untranslatable(const void** src, size_t* size) const;
};
extern TextTranscoder tt_8859_to_utf8;
extern TextTranscoder tt_utf8_to_8859;
extern TextTranscoder tt_standard_sjis_to_utf8;
extern TextTranscoder tt_utf8_to_standard_sjis;
extern TextTranscoderCustomSJISToUTF8 tt_sega_sjis_to_utf8;
extern TextTranscoderUTF8ToCustomSJIS tt_utf8_to_sega_sjis;
extern TextTranscoder tt_utf16_to_utf8;
extern TextTranscoder tt_utf8_to_utf16;
extern TextTranscoder tt_ascii_to_utf8;
extern TextTranscoder tt_utf8_to_ascii;
extern thread_local TextTranscoder tt_8859_to_utf8;
extern thread_local TextTranscoder tt_utf8_to_8859;
extern thread_local TextTranscoder tt_standard_sjis_to_utf8;
extern thread_local TextTranscoder tt_utf8_to_standard_sjis;
extern thread_local TextTranscoderCustomSJISToUTF8 tt_sega_sjis_to_utf8;
extern thread_local TextTranscoderUTF8ToCustomSJIS tt_utf8_to_sega_sjis;
extern thread_local TextTranscoder tt_utf16_to_utf8;
extern thread_local TextTranscoder tt_utf8_to_utf16;
extern thread_local TextTranscoder tt_ascii_to_utf8;
extern thread_local TextTranscoder tt_utf8_to_ascii;
std::string tt_encode_marked_optional(const std::string& utf8, Language default_language, bool is_utf16);
std::string tt_encode_marked(const std::string& utf8, Language default_language, bool is_utf16);
+22 -22
View File
@@ -1339,35 +1339,35 @@
// doesn't have direct access to the client's quest flags from their save file.
// If you use an expression, the format is the same as the AvailableIf and EnabledIf fields in quest JSONs (see
// system/quests/retrieval/q058.json for details). Note that the expression is only evaluated at the time the game is
// created, and the player-specific tokens like C_EpX_YY refer to the player who created the game.
// created, and the player-specific tokens like CC_EpX_YY refer to the player who created the game.
// The UnlockAllAreas option is now gone; if you want the same behavior as if it were enabled, uncomment all the
// "area unlocks" lines below. Note that some late PSOBB client versions (for example, the Tethealla client) open all
// "area unlock" lines below. Note that some late PSOBB client versions (for example, the Tethealla client) open all
// areas by default, so the area unlock flags have no effect for them.
"QuestFlagRewritesV1V2": {
// "F_0017": true, // Ep1 area unlocks
// "F_0020": true, // Ep1 area unlocks
// "F_002A": true, // Ep1 area unlocks
// "F_0017": true, // Ep1 area unlock (Caves)
// "F_0020": true, // Ep1 area unlock (Mines)
// "F_002A": true, // Ep1 area unlock (Ruins)
},
"QuestFlagRewritesV3": {
// "F_0017": true, // Ep1 area unlocks
// "F_0020": true, // Ep1 area unlocks
// "F_002A": true, // Ep1 area unlocks
// "F_004C": true, // Ep2 area unlocks
// "F_004F": true, // Ep2 area unlocks
// "F_0052": true, // Ep2 area unlocks
// "F_0017": true, // Ep1 area unlock (Caves)
// "F_0020": true, // Ep1 area unlock (Mines)
// "F_002A": true, // Ep1 area unlock (Ruins)
// "F_004C": true, // Ep2 area unlock (VR Spaceship)
// "F_004F": true, // Ep2 area unlock (CCA)
// "F_0052": true, // Ep2 area unlock (Seabed)
},
"QuestFlagRewritesV4": {
// "F_01F9": true, // Ep1 area unlocks
// "F_0201": true, // Ep1 area unlocks
// "F_0207": true, // Ep1 area unlocks
// "F_021B": true, // Ep2 area unlocks
// "F_0225": true, // Ep2 area unlocks
// "F_022F": true, // Ep2 area unlocks
// "F_02BD": true, // Ep4 area unlocks
// "F_02BE": true, // Ep4 area unlocks
// "F_02BF": true, // Ep4 area unlocks
// "F_02C0": true, // Ep4 area unlocks
// "F_02C1": true, // Ep4 area unlocks
// "F_01F9": true, // Ep1 area unlock (Caves)
// "F_0201": true, // Ep1 area unlock (Mines)
// "F_0207": true, // Ep1 area unlock (Ruins)
// "F_021B": true, // Ep2 area unlock (VR Spaceship)
// "F_0225": true, // Ep2 area unlock (CCA)
// "F_022F": true, // Ep2 area unlock (Seabed)
// "F_02BD": true, // Ep4 area unlock (Crater West)
// "F_02BE": true, // Ep4 area unlock (Crater South)
// "F_02BF": true, // Ep4 area unlock (Crater North)
// "F_02C0": true, // Ep4 area unlock (Crater Interior)
// "F_02C1": true, // Ep4 area unlock (Desert)
"F_0046": false, // Ep2 CCA door lock fix
"F_0047": false, // Ep2 CCA door lock fix
"F_0048": false, // Ep2 CCA door lock fix
+4 -1
View File
@@ -5,7 +5,10 @@
// Quests that are considered unavailable don't appear in the quest menu at all. To set a condition for a quest to be
// available, you can set AvailableIf in the quest's JSON file. The value for AvailableIf should be an expression
// that tests any of the following:
// F_XXXX: Quest flag specified in hex (e.g. F_014D)
// P_SID: Current effective section ID for the game; see name_to_section_id in StaticGameData.cc for values
// F_XXXX: Quest flag specified in hex (e.g. F_014D) for the difficulty the player is currently playing in
// F_N_XXXX, F_H_XXXX, F_V_XXXX, F_U_XXXX: Same as F_XXXX, but read from a specific difficulty regardless of which
// difficulty the player is currently playing in
// CC_EpX_Y: Whether or not Challenge stage X in Episode Y is complete (e.g. CC_Ep1_7)
// T_ZZZ: Whether or not the player's BB team has reward with key ZZZ (as defined in TeamRewards in config.json)
// V_NumPlayers: The number of players in the current game
+1 -1
View File
@@ -1,6 +1,6 @@
{
// This file specifies how non-rare items should be generated. For details on how each field works, see the comments
// in CommonItemSet::Table::OffsetsT (in CommonItemSet.hh).
// in CommonItemSet::Table::RootT (in CommonItemSet.hh).
// Each scenario has defaults which are automatically propagated from the previous scenario unless overridden. The
// order of precedence is:
// - If the section ID is not Viridia, the defaults are from the same episode, game mode, and difficulty, but
+1
View File
@@ -411,6 +411,7 @@ I 72448 2025-05-20 20:08:45 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.
0010 | A6 31 06 C2 00 00 00 00 A0 4C 89 3E | 1 L >
I 72448 2025-05-20 20:08:46 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.16.0.30:64731 (version=DC_11_2000 command=60 flag=00)
0000 | 60 00 10 00 4C 03 00 00 0A 00 00 00 00 40 00 00 | ` L @
### cc @variations 00020311101000212120000000000090
I 72448 2025-05-20 20:09:02 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.16.0.30:64731 (version=DC_11_2000 command=0C flag=03)
0000 | 0C 03 2C 00 00 00 00 00 00 00 00 00 41 41 41 41 | , AAAA
0010 | 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | A