Merge remote-tracking branch 'upstream/master' into feature/upstream-master-20260614
# Conflicts: # src/Client.cc # src/IPStackSimulator.cc # src/ReceiveCommands.cc # src/ReceiveSubcommands.cc # src/SendCommands.cc # src/ServerState.cc # src/ServerState.hh
This commit is contained in:
+4
-9
@@ -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})
|
||||
list(TRANSFORM LOG_TEST_CASES PREPEND "--replay-log=" OUTPUT_VARIABLE LOG_REPLAY_ARGS)
|
||||
add_test(
|
||||
NAME ${LOG_TEST_CASE}
|
||||
NAME "log-replays"
|
||||
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})
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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,13 +531,7 @@ 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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -14,8 +14,7 @@
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
DNSServer::DNSServer(std::shared_ptr<ServerState> state)
|
||||
: state(state) {}
|
||||
DNSServer::DNSServer(std::shared_ptr<ServerState> state) : state(state) {}
|
||||
|
||||
void DNSServer::listen(const std::string& addr, int port) {
|
||||
if (port == 0) {
|
||||
@@ -62,11 +61,11 @@ asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::
|
||||
if (bytes < 0x0C) {
|
||||
dns_server_log.warning_f("input query too small");
|
||||
phosg::print_data(stderr, input.data(), bytes);
|
||||
} else if (!this->state->banned_ipv4_ranges->check(sender_addr)) {
|
||||
} else if (!this->state->data->banned_ipv4_ranges->check(sender_addr)) {
|
||||
input.resize(bytes);
|
||||
uint32_t connect_address = is_local_address(sender_addr)
|
||||
? this->state->local_address
|
||||
: this->state->external_address;
|
||||
? this->state->data->local_address
|
||||
: this->state->data->external_address;
|
||||
std::string response = this->response_for_query(input, connect_address);
|
||||
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
struct ServerState;
|
||||
class ServerState;
|
||||
|
||||
class DNSServer {
|
||||
public:
|
||||
|
||||
+2063
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
};
|
||||
@@ -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},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
struct Lobby;
|
||||
class Client;
|
||||
struct ServerState;
|
||||
class ServerState;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
|
||||
+2
-2
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
+316
-202
@@ -2075,10 +2075,10 @@ Action a_print_word_select_table(
|
||||
given, prints the table sorted by token ID for that version. If no version\n\
|
||||
option is given, prints the token table sorted by canonical name.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_word_select_table();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_patch_indexes();
|
||||
di->load_text_index();
|
||||
di->load_word_select_table();
|
||||
Version v;
|
||||
try {
|
||||
v = get_cli_version(args);
|
||||
@@ -2086,9 +2086,9 @@ Action a_print_word_select_table(
|
||||
v = Version::UNKNOWN;
|
||||
}
|
||||
if (v != Version::UNKNOWN) {
|
||||
s->word_select_table->print_index(stdout, v);
|
||||
di->word_select_table->print_index(stdout, v);
|
||||
} else {
|
||||
s->word_select_table->print(stdout);
|
||||
di->word_select_table->print(stdout);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2209,20 +2209,20 @@ Action a_convert_rare_item_set(
|
||||
drop-anything rate; the true drop rates are shown in tooltips.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
double rate_factor = args.get<double>("multiply", 1.0);
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_item_definitions();
|
||||
s->load_item_name_indexes();
|
||||
s->load_drop_tables();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_text_index();
|
||||
di->load_item_definitions();
|
||||
di->load_item_name_indexes();
|
||||
di->load_drop_tables();
|
||||
|
||||
std::string input_filename = args.get<std::string>(1, false);
|
||||
if (input_filename.empty() || (input_filename == "-")) {
|
||||
throw std::runtime_error("input filename must be given");
|
||||
}
|
||||
auto rs = load_rare_item_set(
|
||||
input_filename, is_v1(get_cli_version(args, Version::BB_V4)), s->item_name_index(Version::BB_V4));
|
||||
input_filename, is_v1(get_cli_version(args, Version::BB_V4)), di->item_name_index(Version::BB_V4));
|
||||
if (rate_factor != 1.0) {
|
||||
rs->multiply_all_rates(rate_factor);
|
||||
}
|
||||
@@ -2230,9 +2230,9 @@ Action a_convert_rare_item_set(
|
||||
std::string output_filename = args.get<std::string>(2, false);
|
||||
std::string output_filename_lower = phosg::tolower(output_filename);
|
||||
if (output_filename.empty() || (output_filename == "-")) {
|
||||
rs->print_all_collections(stdout, s->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
|
||||
rs->print_all_collections(stdout, di->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
|
||||
} else if (output_filename_lower.ends_with(".json")) {
|
||||
auto json = rs->json(s->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
|
||||
auto json = rs->json(di->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
|
||||
std::string data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
write_output_data(args, data, nullptr);
|
||||
} else if (output_filename_lower.ends_with(".gsl")) {
|
||||
@@ -2251,8 +2251,8 @@ Action a_convert_rare_item_set(
|
||||
if ((is_v1 && (difficulty == Difficulty::ULTIMATE)) || (!rs->has_entries_for_game_config(mode, episode, difficulty))) {
|
||||
continue;
|
||||
}
|
||||
auto item_name_index = s->item_name_index(cli_version);
|
||||
std::string data = rs->serialize_html(mode, episode, difficulty, item_name_index, s->common_item_set(cli_version, nullptr));
|
||||
auto item_name_index = di->item_name_index(cli_version);
|
||||
std::string data = rs->serialize_html(mode, episode, difficulty, item_name_index, di->common_item_set(cli_version, nullptr));
|
||||
std::string out_filename = output_filename.substr(0, output_filename.size() - 5) + "." + name_for_mode(mode) + "." + abbreviation_for_episode(episode) + "." + abbreviation_for_difficulty(difficulty) + output_filename.substr(output_filename.size() - 5);
|
||||
phosg::save_file(out_filename, data);
|
||||
phosg::log_info_f("... {}", out_filename);
|
||||
@@ -2275,17 +2275,17 @@ Action a_compare_rare_item_set(
|
||||
throw std::runtime_error("two input filenames must be given");
|
||||
}
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_item_definitions();
|
||||
s->load_item_name_indexes();
|
||||
s->load_drop_tables();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_text_index();
|
||||
di->load_item_definitions();
|
||||
di->load_item_name_indexes();
|
||||
di->load_drop_tables();
|
||||
|
||||
bool is_v1 = ::is_v1(get_cli_version(args, Version::BB_V4));
|
||||
auto rs1 = load_rare_item_set(input_filename1, is_v1, s->item_name_index(Version::BB_V4));
|
||||
auto rs2 = load_rare_item_set(input_filename2, is_v1, s->item_name_index(Version::BB_V4));
|
||||
auto rs1 = load_rare_item_set(input_filename1, is_v1, di->item_name_index(Version::BB_V4));
|
||||
auto rs2 = load_rare_item_set(input_filename2, is_v1, di->item_name_index(Version::BB_V4));
|
||||
|
||||
rs1->print_diff(stdout, *rs2);
|
||||
});
|
||||
@@ -2622,13 +2622,13 @@ Action a_describe_item(
|
||||
std::string description = args.get<std::string>(1);
|
||||
auto version = get_cli_version(args);
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_item_definitions();
|
||||
s->load_item_name_indexes();
|
||||
auto name_index = s->item_name_index(version);
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_text_index();
|
||||
di->load_item_definitions();
|
||||
di->load_item_name_indexes();
|
||||
auto name_index = di->item_name_index(version);
|
||||
|
||||
ItemData item = name_index->parse_item_description(description);
|
||||
|
||||
@@ -2646,7 +2646,7 @@ Action a_describe_item(
|
||||
item.data2[0], item.data2[1], item.data2[2], item.data2[3]);
|
||||
|
||||
ItemData item_v2 = item;
|
||||
item_v2.encode_for_version(Version::PC_V2, s->item_parameter_table_for_encode(Version::PC_V2));
|
||||
item_v2.encode_for_version(Version::PC_V2, di->item_parameter_table_for_encode(Version::PC_V2));
|
||||
ItemData item_v2_decoded = item_v2;
|
||||
item_v2_decoded.decode_for_version(Version::PC_V2);
|
||||
|
||||
@@ -2665,7 +2665,7 @@ Action a_describe_item(
|
||||
}
|
||||
|
||||
ItemData item_gc = item;
|
||||
item_gc.encode_for_version(Version::GC_V3, s->item_parameter_table_for_encode(Version::GC_V3));
|
||||
item_gc.encode_for_version(Version::GC_V3, di->item_parameter_table_for_encode(Version::GC_V3));
|
||||
ItemData item_gc_decoded = item_gc;
|
||||
item_gc_decoded.decode_for_version(Version::GC_V3);
|
||||
|
||||
@@ -2686,24 +2686,24 @@ Action a_describe_item(
|
||||
phosg::log_info_f("Description: {}", desc);
|
||||
phosg::log_info_f("Description (in-game): {}", desc_colored);
|
||||
|
||||
size_t purchase_price = s->item_parameter_table(Version::BB_V4)->price_for_item(item);
|
||||
size_t purchase_price = di->item_parameter_table(Version::BB_V4)->price_for_item(item);
|
||||
size_t sale_price = purchase_price >> 3;
|
||||
phosg::log_info_f("Purchase price: {}; sale price: {}", purchase_price, sale_price);
|
||||
});
|
||||
|
||||
Action a_name_all_items(
|
||||
"name-all-items", nullptr, +[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_item_definitions();
|
||||
s->load_item_name_indexes();
|
||||
s->load_ep3_cards();
|
||||
s->load_config_late();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_text_index();
|
||||
di->load_item_definitions();
|
||||
di->load_item_name_indexes();
|
||||
di->load_ep3_cards();
|
||||
di->load_config_late();
|
||||
|
||||
std::set<uint32_t> all_primary_identifiers;
|
||||
for (const auto& index : s->item_name_indexes) {
|
||||
for (const auto& index : di->item_name_indexes) {
|
||||
if (index) {
|
||||
for (const auto& it : index->all_by_primary_identifier()) {
|
||||
all_primary_identifiers.emplace(it.first);
|
||||
@@ -2715,10 +2715,10 @@ Action a_name_all_items(
|
||||
for (uint32_t primary_identifier : all_primary_identifiers) {
|
||||
phosg::fwrite_fmt(stdout, "{:08X}\n", primary_identifier);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
const auto& index = di->item_name_index_opt(v);
|
||||
if (index) {
|
||||
auto pmt = s->item_parameter_table(v);
|
||||
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier);
|
||||
auto pmt = di->item_parameter_table(v);
|
||||
ItemData item = ItemData::from_primary_identifier(*di->item_stack_limits(v), primary_identifier);
|
||||
std::string name = index->describe_item(item);
|
||||
try {
|
||||
bool is_rare = pmt->is_item_rare(item);
|
||||
@@ -2736,7 +2736,7 @@ Action a_name_all_items(
|
||||
auto print_header = [&]() -> void {
|
||||
phosg::fwrite_fmt(stdout, "IDENT :");
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
const auto& index = di->item_name_index_opt(v);
|
||||
if (index) {
|
||||
phosg::fwrite_fmt(stdout, " {:30} ", phosg::name_for_enum(v));
|
||||
}
|
||||
@@ -2755,10 +2755,10 @@ Action a_name_all_items(
|
||||
|
||||
phosg::fwrite_fmt(stdout, "{:08X}:", primary_identifier);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
const auto& index = di->item_name_index_opt(v);
|
||||
if (index) {
|
||||
auto pmt = s->item_parameter_table(v);
|
||||
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier);
|
||||
auto pmt = di->item_parameter_table(v);
|
||||
ItemData item = ItemData::from_primary_identifier(*di->item_stack_limits(v), primary_identifier);
|
||||
if (index->exists(item)) {
|
||||
std::string name = index->describe_item(item);
|
||||
bool is_rare = pmt->is_item_rare(item);
|
||||
@@ -2778,10 +2778,10 @@ Action a_print_level_stats(
|
||||
show-level-tables\n\
|
||||
Print the level tables for each version in a semi-human-readable format.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_level_tables();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_level_tables();
|
||||
|
||||
std::vector<PlayerStats> level_1_v1_v2;
|
||||
std::vector<PlayerStats> level_100_v1_v2;
|
||||
@@ -2795,19 +2795,19 @@ Action a_print_level_stats(
|
||||
std::vector<PlayerStats> level_200_limit_v4;
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (z < 9) {
|
||||
level_1_v1_v2.emplace_back().char_stats = s->level_table_v1_v2->base_stats_for_class(z);
|
||||
level_200_limit_v1_v2.emplace_back(s->level_table_v1_v2->max_stats_for_class(z));
|
||||
s->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
|
||||
s->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
|
||||
level_1_v1_v2.emplace_back().char_stats = di->level_table_v1_v2->base_stats_for_class(z);
|
||||
level_200_limit_v1_v2.emplace_back(di->level_table_v1_v2->max_stats_for_class(z));
|
||||
di->level_table_v1_v2->advance_to_level(level_100_v1_v2.emplace_back(level_1_v1_v2.back()), 99, z);
|
||||
di->level_table_v1_v2->advance_to_level(level_200_v1_v2.emplace_back(level_1_v1_v2.back()), 199, z);
|
||||
}
|
||||
|
||||
level_1_v3.emplace_back().char_stats = s->level_table_v3->base_stats_for_class(z);
|
||||
s->level_table_v3->advance_to_level(level_200_v3.emplace_back(level_1_v3.back()), 199, z);
|
||||
level_200_limit_v3.emplace_back(s->level_table_v3->max_stats_for_class(z));
|
||||
level_1_v3.emplace_back().char_stats = di->level_table_v3->base_stats_for_class(z);
|
||||
di->level_table_v3->advance_to_level(level_200_v3.emplace_back(level_1_v3.back()), 199, z);
|
||||
level_200_limit_v3.emplace_back(di->level_table_v3->max_stats_for_class(z));
|
||||
|
||||
level_1_v4.emplace_back().char_stats = s->level_table_v4->base_stats_for_class(z);
|
||||
s->level_table_v4->advance_to_level(level_200_v4.emplace_back(level_1_v3.back()), 199, z);
|
||||
level_200_limit_v4.emplace_back(s->level_table_v4->max_stats_for_class(z));
|
||||
level_1_v4.emplace_back().char_stats = di->level_table_v4->base_stats_for_class(z);
|
||||
di->level_table_v4->advance_to_level(level_200_v4.emplace_back(level_1_v3.back()), 199, z);
|
||||
level_200_limit_v4.emplace_back(di->level_table_v4->max_stats_for_class(z));
|
||||
}
|
||||
|
||||
auto print_stats_set = [](const std::vector<PlayerStats>& stats_vec, const char* name) -> void {
|
||||
@@ -2865,10 +2865,10 @@ Action a_show_item_parameter_tables(
|
||||
Print the item parameter tables for each version in a semi-human-readable\n\
|
||||
format.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_all(false);
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_all();
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
const auto& index = di->item_name_index_opt(v);
|
||||
if (index) {
|
||||
phosg::fwrite_fmt(stdout, "======== {}\n", phosg::name_for_enum(v));
|
||||
index->print_table(stdout);
|
||||
@@ -2882,19 +2882,19 @@ Action a_show_shop_random_sets(
|
||||
Print the tekker and shop generation tables in a semi-human-readable\n\
|
||||
format.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_all(false);
|
||||
s->tekker_adjustment_set->print(stdout);
|
||||
s->armor_random_set->print(stdout);
|
||||
s->tool_random_set->print(stdout);
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_all();
|
||||
di->tekker_adjustment_set->print(stdout);
|
||||
di->armor_random_set->print(stdout);
|
||||
di->tool_random_set->print(stdout);
|
||||
phosg::fwrite_fmt(stdout, "(Normal) ");
|
||||
s->weapon_random_set(Difficulty::NORMAL)->print(stdout);
|
||||
di->weapon_random_set(Difficulty::NORMAL)->print(stdout);
|
||||
phosg::fwrite_fmt(stdout, "(Hard) ");
|
||||
s->weapon_random_set(Difficulty::HARD)->print(stdout);
|
||||
di->weapon_random_set(Difficulty::HARD)->print(stdout);
|
||||
phosg::fwrite_fmt(stdout, "(Very Hard) ");
|
||||
s->weapon_random_set(Difficulty::VERY_HARD)->print(stdout);
|
||||
di->weapon_random_set(Difficulty::VERY_HARD)->print(stdout);
|
||||
phosg::fwrite_fmt(stdout, "(Ultimate) ");
|
||||
s->weapon_random_set(Difficulty::ULTIMATE)->print(stdout);
|
||||
di->weapon_random_set(Difficulty::ULTIMATE)->print(stdout);
|
||||
});
|
||||
|
||||
Action a_show_ep3_cards(
|
||||
@@ -2905,8 +2905,8 @@ Action a_show_ep3_cards(
|
||||
+[](phosg::Arguments& args) {
|
||||
bool one_line = args.get<bool>("one-line");
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_ep3_cards();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_ep3_cards();
|
||||
|
||||
std::unique_ptr<BinaryTextSet> text_english;
|
||||
try {
|
||||
@@ -2915,10 +2915,10 @@ Action a_show_ep3_cards(
|
||||
} catch (const std::exception& e) {
|
||||
}
|
||||
|
||||
auto card_ids = s->ep3_card_index->all_ids();
|
||||
auto card_ids = di->ep3_card_index->all_ids();
|
||||
phosg::log_info_f("{} card definitions", card_ids.size());
|
||||
for (uint32_t card_id : card_ids) {
|
||||
auto entry = s->ep3_card_index->definition_for_id(card_id);
|
||||
auto entry = di->ep3_card_index->definition_for_id(card_id);
|
||||
phosg::fwrite_fmt(stdout, "{}\n", entry->def.str(one_line, text_english.get()));
|
||||
if (!one_line) {
|
||||
if (!entry->debug_tags.empty()) {
|
||||
@@ -2957,14 +2957,14 @@ Action a_generate_ep3_cards_html(
|
||||
bool no_large_images = args.get<bool>("no-large-images");
|
||||
bool no_disassembly = args.get<bool>("no-disassembly");
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_ep3_cards();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_patch_indexes();
|
||||
di->load_text_index();
|
||||
di->load_ep3_cards();
|
||||
|
||||
std::shared_ptr<const TextSet> text_english;
|
||||
try {
|
||||
text_english = s->text_index->get(Version::GC_EP3, Language::ENGLISH);
|
||||
text_english = di->text_index->get(Version::GC_EP3, Language::ENGLISH);
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
@@ -3063,10 +3063,10 @@ Action a_generate_ep3_cards_html(
|
||||
|
||||
std::vector<VersionInfo> version_infos;
|
||||
if (include_nte) {
|
||||
version_infos.emplace_back("NTE", s->ep3_card_index_trial, no_images ? nullptr : "system/ep3/cardtex-trial", no_large_images, num_threads, no_disassembly);
|
||||
version_infos.emplace_back("NTE", di->ep3_card_index_trial, no_images ? nullptr : "system/ep3/cardtex-trial", no_large_images, num_threads, no_disassembly);
|
||||
}
|
||||
if (include_final) {
|
||||
version_infos.emplace_back("Final", s->ep3_card_index, no_images ? nullptr : "system/ep3/cardtex", no_large_images, num_threads, no_disassembly);
|
||||
version_infos.emplace_back("Final", di->ep3_card_index, no_images ? nullptr : "system/ep3/cardtex", no_large_images, num_threads, no_disassembly);
|
||||
}
|
||||
|
||||
std::deque<std::string> blocks;
|
||||
@@ -3199,11 +3199,11 @@ Action a_show_ep3_maps(
|
||||
+[](phosg::Arguments& args) {
|
||||
config_log.info_f("Collecting Episode 3 data");
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_ep3_cards();
|
||||
s->load_ep3_maps();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_ep3_cards();
|
||||
di->load_ep3_maps();
|
||||
|
||||
const auto& all_maps = s->ep3_map_index->all_maps();
|
||||
const auto& all_maps = di->ep3_map_index->all_maps();
|
||||
phosg::log_info_f("{} maps", all_maps.size());
|
||||
for (const auto& [map_number, map] : all_maps) {
|
||||
const auto& vms = map->all_versions();
|
||||
@@ -3212,7 +3212,7 @@ Action a_show_ep3_maps(
|
||||
continue;
|
||||
}
|
||||
Language language = static_cast<Language>(lang_index);
|
||||
std::string map_s = vms[lang_index]->map->str(s->ep3_card_index.get(), language);
|
||||
std::string map_s = vms[lang_index]->map->str(di->ep3_card_index.get(), language);
|
||||
phosg::fwrite_fmt(stdout, "({}) {}\n", char_for_language(language), map_s);
|
||||
}
|
||||
}
|
||||
@@ -3224,22 +3224,22 @@ Action a_show_battle_params(
|
||||
Print the Blue Burst battle parameters from the system/blueburst directory\n\
|
||||
in a human-readable format.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_patch_indexes();
|
||||
s->load_battle_params();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_patch_indexes();
|
||||
di->load_battle_params();
|
||||
|
||||
phosg::fwrite_fmt(stdout, "Episode 1 multi\n");
|
||||
s->battle_params->get_table(false, Episode::EP1).print(stdout, Episode::EP1);
|
||||
di->battle_params->get_table(false, Episode::EP1).print(stdout, Episode::EP1);
|
||||
phosg::fwrite_fmt(stdout, "Episode 1 solo\n");
|
||||
s->battle_params->get_table(true, Episode::EP1).print(stdout, Episode::EP1);
|
||||
di->battle_params->get_table(true, Episode::EP1).print(stdout, Episode::EP1);
|
||||
phosg::fwrite_fmt(stdout, "Episode 2 multi\n");
|
||||
s->battle_params->get_table(false, Episode::EP2).print(stdout, Episode::EP2);
|
||||
di->battle_params->get_table(false, Episode::EP2).print(stdout, Episode::EP2);
|
||||
phosg::fwrite_fmt(stdout, "Episode 2 solo\n");
|
||||
s->battle_params->get_table(true, Episode::EP2).print(stdout, Episode::EP2);
|
||||
di->battle_params->get_table(true, Episode::EP2).print(stdout, Episode::EP2);
|
||||
phosg::fwrite_fmt(stdout, "Episode 4 multi\n");
|
||||
s->battle_params->get_table(false, Episode::EP4).print(stdout, Episode::EP4);
|
||||
di->battle_params->get_table(false, Episode::EP4).print(stdout, Episode::EP4);
|
||||
phosg::fwrite_fmt(stdout, "Episode 4 solo\n");
|
||||
s->battle_params->get_table(true, Episode::EP4).print(stdout, Episode::EP4);
|
||||
di->battle_params->get_table(true, Episode::EP4).print(stdout, Episode::EP4);
|
||||
});
|
||||
|
||||
Action a_check_supermaps(
|
||||
@@ -3253,11 +3253,11 @@ Action a_check_supermaps(
|
||||
bool save_disassembly = args.get<bool>("disassemble");
|
||||
bool generate_enemy_stats = args.get<bool>("generate-enemy-stats");
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_set_data_tables();
|
||||
s->load_maps();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_set_data_tables();
|
||||
di->load_maps();
|
||||
|
||||
auto rand_crypt = std::make_shared<MT19937Generator>(phosg::random_object<uint32_t>());
|
||||
|
||||
@@ -3273,9 +3273,9 @@ Action a_check_supermaps(
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
|
||||
auto sdt = s->set_data_table(Version::BB_V4, episode, mode, difficulty);
|
||||
auto sdt = di->set_data_table(Version::BB_V4, episode, mode, difficulty);
|
||||
auto variations = sdt->generate_variations(episode, (mode == GameMode::SOLO), rand_crypt);
|
||||
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations);
|
||||
auto supermaps = di->supermaps_for_variations(episode, mode, difficulty, variations);
|
||||
auto map_state = std::make_shared<MapState>(
|
||||
0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps);
|
||||
map_state->verify();
|
||||
@@ -3288,7 +3288,7 @@ Action a_check_supermaps(
|
||||
}
|
||||
|
||||
SuperMap::EfficiencyStats all_free_maps_eff;
|
||||
for (const auto& [key, supermap] : s->supermap_for_free_play_key) {
|
||||
for (const auto& [key, supermap] : di->supermap_for_free_play_key) {
|
||||
auto episode = static_cast<Episode>((key >> 28) & 7);
|
||||
auto mode = static_cast<GameMode>((key >> 26) & 3);
|
||||
Difficulty difficulty = static_cast<Difficulty>((key >> 24) & 3);
|
||||
@@ -3332,11 +3332,11 @@ Action a_check_supermaps(
|
||||
|
||||
phosg::fwrite_fmt(stderr, "ALL FREE MAPS: {}\n", all_free_maps_eff.str());
|
||||
|
||||
s->load_quest_index();
|
||||
di->load_quest_index();
|
||||
|
||||
SuperMap::EfficiencyStats all_quests_eff;
|
||||
uint32_t random_seed = args.get<uint32_t>("random-seed", 0, phosg::Arguments::IntFormat::HEX);
|
||||
for (const auto& it : s->quest_index->quests_by_number) {
|
||||
for (const auto& it : di->quest_index->quests_by_number) {
|
||||
auto supermap = it.second->get_supermap(random_seed);
|
||||
if (!supermap) {
|
||||
throw std::logic_error("quest does not have a supermap, even with a specified random seed");
|
||||
@@ -3648,11 +3648,11 @@ Action a_print_free_supermap(
|
||||
}
|
||||
}
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_set_data_tables();
|
||||
s->load_maps();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_set_data_tables();
|
||||
di->load_maps();
|
||||
|
||||
std::shared_ptr<RandomGenerator> rand_crypt;
|
||||
if (args.get<bool>("--psov2")) {
|
||||
@@ -3660,8 +3660,8 @@ Action a_print_free_supermap(
|
||||
} else {
|
||||
rand_crypt = std::make_shared<PSOV2Encryption>(random_seed);
|
||||
}
|
||||
auto sdt = s->set_data_table(get_cli_version(args, Version::BB_V4), episode, mode, difficulty);
|
||||
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations);
|
||||
auto sdt = di->set_data_table(get_cli_version(args, Version::BB_V4), episode, mode, difficulty);
|
||||
auto supermaps = di->supermaps_for_variations(episode, mode, difficulty, variations);
|
||||
MapState map_state(0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, rand_crypt, supermaps);
|
||||
map_state.verify();
|
||||
map_state.print(stdout);
|
||||
@@ -3670,25 +3670,31 @@ Action a_print_free_supermap(
|
||||
Action a_check_quests(
|
||||
"check-quests", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
check_quest_opcode_definitions();
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->is_debug = true;
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_set_data_tables();
|
||||
s->load_maps();
|
||||
s->load_quest_index(true);
|
||||
|
||||
size_t num_threads = args.get<size_t>("threads", 0);
|
||||
bool reassemble_scripts = args.get<bool>("reassemble-scripts");
|
||||
bool reassemble_maps = args.get<bool>("reassemble-maps");
|
||||
|
||||
check_quest_opcode_definitions();
|
||||
phosg::log_info_f("Opcode definitions OK");
|
||||
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->is_debug = true;
|
||||
di->load_config_early();
|
||||
di->load_patch_indexes();
|
||||
di->load_set_data_tables();
|
||||
di->load_maps();
|
||||
di->load_quest_index(true);
|
||||
|
||||
uint64_t script_time = 0, map_time = 0;
|
||||
if (reassemble_scripts || reassemble_maps) {
|
||||
for (const auto& [_, q] : s->quest_index->quests_by_number) {
|
||||
for (const auto& [_, vq] : q->versions) {
|
||||
std::mutex output_lock;
|
||||
auto check_vq = [&](const std::shared_ptr<const VersionedQuest>& vq, size_t) -> void {
|
||||
if (reassemble_maps) {
|
||||
uint64_t start_time = phosg::now();
|
||||
auto dat = prs_decompress(*vq->dat_contents);
|
||||
auto serialized = vq->map_file->serialize();
|
||||
if (dat != serialized) {
|
||||
std::lock_guard g(output_lock);
|
||||
phosg::log_info_f("... DISASSEMBLY:");
|
||||
phosg::fwritex(stdout, vq->map_file->disassemble(false, vq->meta.version));
|
||||
phosg::log_info_f("... BINDIFF:");
|
||||
@@ -3701,9 +3707,18 @@ Action a_check_quests(
|
||||
vq->meta.name);
|
||||
throw std::runtime_error("re-serialized map file differs from original");
|
||||
}
|
||||
phosg::log_info_f("... {} {} {} ({}) MAP OK", phosg::name_for_enum(vq->meta.version), name_for_language(vq->meta.language), vq->dat_filename(), vq->meta.name);
|
||||
uint64_t end_time = phosg::now();
|
||||
map_time += (end_time - start_time);
|
||||
std::lock_guard g(output_lock);
|
||||
phosg::log_info_f("... {} {} {} ({}) MAP OK ({})",
|
||||
phosg::name_for_enum(vq->meta.version),
|
||||
name_for_language(vq->meta.language),
|
||||
vq->dat_filename(),
|
||||
vq->meta.name,
|
||||
phosg::format_duration(end_time - start_time));
|
||||
}
|
||||
if (reassemble_scripts) {
|
||||
uint64_t start_time = phosg::now();
|
||||
auto bin = prs_decompress(*vq->bin_contents);
|
||||
auto disassembled = disassemble_quest_script(
|
||||
bin.data(), bin.size(), vq->meta.version, vq->meta.language, vq->map_file, false, false);
|
||||
@@ -3757,6 +3772,7 @@ Action a_check_quests(
|
||||
assembled.meta.long_description, vq->meta.long_description));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::lock_guard g(output_lock);
|
||||
phosg::log_error_f("================ DISASSEMBLY:");
|
||||
phosg::fwritex(stderr, disassembled);
|
||||
phosg::log_error_f("================ REASSEMBLY:");
|
||||
@@ -3768,20 +3784,60 @@ Action a_check_quests(
|
||||
phosg::log_info_f("... {} {} {} ({}) SCRIPT FAILED", phosg::name_for_enum(vq->meta.version), name_for_language(vq->meta.language), vq->bin_filename(), vq->meta.name);
|
||||
throw;
|
||||
}
|
||||
phosg::log_info_f("... {} {} {} ({}) SCRIPT OK", phosg::name_for_enum(vq->meta.version), name_for_language(vq->meta.language), vq->bin_filename(), vq->meta.name);
|
||||
uint64_t end_time = phosg::now();
|
||||
script_time += (end_time - start_time);
|
||||
std::lock_guard g(output_lock);
|
||||
phosg::log_info_f("... {} {} {} ({}) SCRIPT OK ({})",
|
||||
phosg::name_for_enum(vq->meta.version),
|
||||
name_for_language(vq->meta.language),
|
||||
vq->bin_filename(),
|
||||
vq->meta.name,
|
||||
phosg::format_duration(end_time - start_time));
|
||||
}
|
||||
};
|
||||
|
||||
if (num_threads == 1) {
|
||||
for (const auto& [_, q] : di->quest_index->quests_by_number) {
|
||||
for (const auto& [_, vq] : q->versions) {
|
||||
check_vq(vq, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
std::vector<std::shared_ptr<const VersionedQuest>> all_vqs;
|
||||
for (const auto& [_, q] : di->quest_index->quests_by_number) {
|
||||
for (const auto& [_, vq] : q->versions) {
|
||||
all_vqs.emplace_back(vq);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort them in decreasing order of bin file size, so the slowest ones are run first (this packs the work
|
||||
// into the threads' timelines more efficiently)
|
||||
std::sort(all_vqs.begin(), all_vqs.end(), [](const std::shared_ptr<const VersionedQuest>& a, const std::shared_ptr<const VersionedQuest>& b) -> bool {
|
||||
return a->bin_contents->size() > b->bin_contents->size();
|
||||
});
|
||||
|
||||
phosg::parallel_range(all_vqs, [&check_vq](const auto& vq, size_t index) -> bool {
|
||||
check_vq(vq, index);
|
||||
return false;
|
||||
}, num_threads);
|
||||
}
|
||||
}
|
||||
if (script_time > 0) {
|
||||
phosg::log_info_f("... SCRIPT CHECKS: {}", phosg::format_duration(script_time));
|
||||
}
|
||||
if (map_time > 0) {
|
||||
phosg::log_info_f("... MAP CHECKS: {}", phosg::format_duration(map_time));
|
||||
}
|
||||
});
|
||||
|
||||
Action a_check_ep3_maps(
|
||||
"check-ep3-maps", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
config_log.info_f("Collecting Episode 3 data");
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->is_debug = true;
|
||||
s->load_ep3_maps(true);
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->is_debug = true;
|
||||
di->load_ep3_maps(true);
|
||||
});
|
||||
|
||||
Action a_check_client_functions(
|
||||
@@ -3976,9 +4032,9 @@ Action a_format_ep3_battle_record(
|
||||
|
||||
Action a_replay_ep3_battle_commands(
|
||||
"replay-ep3-battle-commands", nullptr, +[](phosg::Arguments& args) {
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_ep3_cards();
|
||||
s->load_ep3_maps();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_ep3_cards();
|
||||
di->load_ep3_maps();
|
||||
|
||||
int64_t base_seed = args.get<int64_t>("seed", -1);
|
||||
bool is_trial = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE);
|
||||
@@ -3994,8 +4050,8 @@ Action a_replay_ep3_battle_commands(
|
||||
|
||||
auto run_replay = [&](int64_t seed, size_t) {
|
||||
Episode3::Server::Options options = {
|
||||
.card_index = s->ep3_card_index,
|
||||
.map_index = s->ep3_map_index,
|
||||
.card_index = di->ep3_card_index,
|
||||
.map_index = di->ep3_map_index,
|
||||
.behavior_flags = 0x0092,
|
||||
.opt_rand_stream = nullptr,
|
||||
.rand_crypt = std::make_shared<MT19937Generator>(seed),
|
||||
@@ -4035,15 +4091,15 @@ Action a_replay_ep3_battle_record(
|
||||
|
||||
bool use_color = isatty(fileno(stdout));
|
||||
|
||||
auto s = std::make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_ep3_cards();
|
||||
s->load_ep3_maps();
|
||||
auto di = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
di->load_ep3_cards();
|
||||
di->load_ep3_maps();
|
||||
|
||||
bool is_nte = rec->get_behavior_flags() & Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
auto output_queue = std::make_shared<std::deque<std::string>>();
|
||||
Episode3::Server::Options options = {
|
||||
.card_index = s->ep3_card_index,
|
||||
.map_index = s->ep3_map_index,
|
||||
.card_index = di->ep3_card_index,
|
||||
.map_index = di->ep3_map_index,
|
||||
.behavior_flags = rec->get_behavior_flags() & ~(Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING),
|
||||
.opt_rand_stream = std::make_shared<phosg::StringReader>(rec->get_random_stream()),
|
||||
.rand_crypt = std::make_shared<DisabledRandomGenerator>(),
|
||||
@@ -4167,7 +4223,20 @@ Action a_run_server_replay_log(
|
||||
std::filesystem::create_directories("system/players");
|
||||
}
|
||||
|
||||
const auto& replay_log_filenames = args.get_multi<std::string>("replay-log");
|
||||
const auto& args_replay_log_filenames = args.get_multi<std::string>("replay-log");
|
||||
std::vector<std::string> replay_log_filenames;
|
||||
for (auto& log_filename : args_replay_log_filenames) {
|
||||
if (std::filesystem::is_directory(log_filename)) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(log_filename)) {
|
||||
std::string test_filename = item.path().filename().string();
|
||||
if (test_filename.ends_with(".test.txt")) {
|
||||
replay_log_filenames.emplace_back(std::format("{}/{}", log_filename, test_filename));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
replay_log_filenames.emplace_back(std::move(log_filename));
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef PHOSG_WINDOWS
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
@@ -4176,43 +4245,65 @@ Action a_run_server_replay_log(
|
||||
use_terminal_colors = true;
|
||||
}
|
||||
|
||||
auto state = std::make_shared<ServerState>(get_config_filename(args), !replay_log_filenames.empty());
|
||||
auto data_index = std::make_shared<DataIndex>(get_config_filename(args));
|
||||
if (args.get<bool>("debug")) {
|
||||
state->is_debug = true;
|
||||
}
|
||||
state->load_all(true);
|
||||
|
||||
if (state->dns_server_port) {
|
||||
if (!state->dns_server_addr.empty()) {
|
||||
config_log.info_f("Starting DNS server on {}:{}", state->dns_server_addr, state->dns_server_port);
|
||||
} else {
|
||||
config_log.info_f("Starting DNS server on port {}", state->dns_server_port);
|
||||
}
|
||||
state->dns_server = std::make_shared<DNSServer>(state);
|
||||
state->dns_server->listen(state->dns_server_addr, state->dns_server_port);
|
||||
} else {
|
||||
config_log.info_f("DNS server is disabled");
|
||||
data_index->is_debug = true;
|
||||
}
|
||||
data_index->load_all();
|
||||
auto state = ServerState::create_shared(data_index, !replay_log_filenames.empty());
|
||||
|
||||
std::shared_ptr<ServerShell> shell;
|
||||
std::shared_ptr<SignalWatcher> signal_watcher;
|
||||
std::shared_ptr<ReplaySession> last_running_replay;
|
||||
std::map<std::string, std::shared_ptr<ReplaySession>> replay_sessions;
|
||||
if (!replay_log_filenames.empty()) {
|
||||
config_log.info_f("Starting game server");
|
||||
// TODO: Do this properly via a config option, you lazy bum
|
||||
state->data->dol_file_index = std::make_shared<DOLFileIndex>();
|
||||
state->game_server = std::make_shared<GameServer>(state);
|
||||
|
||||
// TODO: Do this properly via a config option, you lazy bum
|
||||
state->dol_file_index = std::make_shared<DOLFileIndex>();
|
||||
if (args.get<bool>("parallel")) {
|
||||
size_t completed_count = 0;
|
||||
auto run_replay = [&](const std::string& log_filename) -> asio::awaitable<void> {
|
||||
auto replay_state = state->clone_shared();
|
||||
replay_state->game_server = std::make_shared<GameServer>(replay_state);
|
||||
|
||||
phosg::log_info_f("[Replay] Loading {}", log_filename);
|
||||
auto log_f = phosg::fopen_unique(log_filename, "rt");
|
||||
auto replay_session = std::make_shared<ReplaySession>(replay_state, log_f.get());
|
||||
replay_sessions.emplace(log_filename, replay_session);
|
||||
|
||||
phosg::log_info_f("[Replay] {} ...", log_filename);
|
||||
co_await replay_session->run();
|
||||
if (!replay_session->failure_str().empty()) {
|
||||
phosg::log_error_f("[Replay] {} failed:\n{}", log_filename, replay_session->failure_str());
|
||||
} else {
|
||||
phosg::log_info_f("[Replay] {} OK", log_filename);
|
||||
}
|
||||
|
||||
completed_count++;
|
||||
if (completed_count == replay_log_filenames.size()) {
|
||||
phosg::log_info_f("[Replay] All replays complete; exiting");
|
||||
state->io_context->stop();
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& log_filename : replay_log_filenames) {
|
||||
asio::co_spawn(*state->io_context, run_replay(log_filename), asio::detached);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (const auto& log_filename : replay_log_filenames) {
|
||||
phosg::log_info_f("[Replay] Loading {}", log_filename);
|
||||
auto log_f = phosg::fopen_unique(log_filename, "rt");
|
||||
replay_sessions.emplace(log_filename, std::make_shared<ReplaySession>(state, log_f.get()));
|
||||
}
|
||||
|
||||
auto run_replays = [&]() -> asio::awaitable<void> {
|
||||
try {
|
||||
for (const auto& log_filename : replay_log_filenames) {
|
||||
for (const auto& [log_filename, replay_session] : replay_sessions) {
|
||||
phosg::log_info_f("[Replay] {} ...", log_filename);
|
||||
auto log_f = phosg::fopen_shared(log_filename, "rt");
|
||||
last_running_replay = std::make_shared<ReplaySession>(state, log_f.get());
|
||||
co_await last_running_replay->run();
|
||||
if (last_running_replay->failed()) {
|
||||
phosg::log_error_f("[Replay] {} failed", log_filename);
|
||||
co_await replay_session->run();
|
||||
if (!replay_session->failure_str().empty()) {
|
||||
phosg::log_error_f("[Replay] {} failed:\n{}", log_filename, replay_session->failure_str());
|
||||
break;
|
||||
}
|
||||
phosg::log_info_f("[Replay] {} OK", log_filename);
|
||||
@@ -4222,77 +4313,89 @@ Action a_run_server_replay_log(
|
||||
} catch (const std::exception& e) {
|
||||
phosg::log_info_f("[Replay] Replays failed: {}", e.what());
|
||||
}
|
||||
if (!last_running_replay->failed()) {
|
||||
last_running_replay.reset();
|
||||
}
|
||||
state->io_context->stop();
|
||||
};
|
||||
asio::co_spawn(*state->io_context, run_replays, asio::detached);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (state->data->dns_server_port) {
|
||||
if (!state->data->dns_server_addr.empty()) {
|
||||
config_log.info_f("Starting DNS server on {}:{}", state->data->dns_server_addr, state->data->dns_server_port);
|
||||
} else {
|
||||
config_log.info_f("Starting DNS server on port {}", state->data->dns_server_port);
|
||||
}
|
||||
state->dns_server = std::make_shared<DNSServer>(state);
|
||||
state->dns_server->listen(state->data->dns_server_addr, state->data->dns_server_port);
|
||||
} else {
|
||||
config_log.info_f("DNS server is disabled");
|
||||
}
|
||||
|
||||
config_log.info_f("Opening sockets");
|
||||
for (const auto& [_, pc] : state->name_to_port_config) {
|
||||
for (const auto& [_, pc] : state->data->name_to_port_config) {
|
||||
if (!state->game_server.get()) {
|
||||
config_log.info_f("Starting game server");
|
||||
state->game_server = std::make_shared<GameServer>(state);
|
||||
}
|
||||
std::string spec = std::format("TG-{}-{}-{}-{}",
|
||||
pc->port, phosg::name_for_enum(pc->version), pc->name, phosg::name_for_enum(pc->behavior));
|
||||
state->game_server->listen(spec, pc->addr, pc->port, pc->version, pc->behavior);
|
||||
pc.port, phosg::name_for_enum(pc.version), pc.name, phosg::name_for_enum(pc.behavior));
|
||||
state->game_server->listen(spec, pc.addr, pc.port, pc.version, pc.behavior);
|
||||
}
|
||||
|
||||
if (!state->ip_stack_addresses.empty() || !state->ppp_stack_addresses.empty() || !state->ppp_raw_addresses.empty()) {
|
||||
if (!state->data->ip_stack_addresses.empty() ||
|
||||
!state->data->ppp_stack_addresses.empty() ||
|
||||
!state->data->ppp_raw_addresses.empty()) {
|
||||
config_log.info_f("Starting IP/PPP stack simulator");
|
||||
state->ip_stack_simulator = std::make_shared<IPStackSimulator>(state);
|
||||
for (const auto& it : state->ip_stack_addresses) {
|
||||
for (const auto& it : state->data->ip_stack_addresses) {
|
||||
auto netloc = phosg::parse_netloc(it);
|
||||
std::string spec = (netloc.second == 0) ? ("T-IPS-" + netloc.first) : std::format("T-IPS-{}", netloc.second);
|
||||
state->ip_stack_simulator->listen(
|
||||
spec, netloc.first, netloc.second, VirtualNetworkProtocol::ETHERNET_TAPSERVER);
|
||||
}
|
||||
for (const auto& it : state->ppp_stack_addresses) {
|
||||
for (const auto& it : state->data->ppp_stack_addresses) {
|
||||
auto netloc = phosg::parse_netloc(it);
|
||||
std::string spec = (netloc.second == 0) ? ("T-PPPST-" + netloc.first) : std::format("T-PPPST-{}", netloc.second);
|
||||
state->ip_stack_simulator->listen(
|
||||
spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_TAPSERVER);
|
||||
}
|
||||
for (const auto& it : state->ppp_raw_addresses) {
|
||||
for (const auto& it : state->data->ppp_raw_addresses) {
|
||||
auto netloc = phosg::parse_netloc(it);
|
||||
std::string spec = (netloc.second == 0) ? ("T-PPPSR-" + netloc.first) : std::format("T-PPPSR-{}", netloc.second);
|
||||
state->ip_stack_simulator->listen(
|
||||
spec, netloc.first, netloc.second, VirtualNetworkProtocol::HDLC_RAW);
|
||||
if (netloc.second) {
|
||||
if (state->local_address == 0 && state->external_address == 0) {
|
||||
if (state->data->local_address == 0 && state->data->external_address == 0) {
|
||||
config_log.info_f(
|
||||
"Cannot generate Devolution phone numbers for {} because LocalAddress and ExternalAddress are not specified in the configuration",
|
||||
spec);
|
||||
} else if (state->local_address == 0) {
|
||||
} else if (state->data->local_address == 0) {
|
||||
config_log.info_f(
|
||||
"Note: The Devolution phone number for {} is {} (external)",
|
||||
spec, devolution_phone_number_for_netloc(state->external_address, netloc.second));
|
||||
} else if (state->external_address == 0) {
|
||||
spec, devolution_phone_number_for_netloc(state->data->external_address, netloc.second));
|
||||
} else if (state->data->external_address == 0) {
|
||||
config_log.info_f(
|
||||
"Note: The Devolution phone number for {} is {} (local)",
|
||||
spec, devolution_phone_number_for_netloc(state->local_address, netloc.second));
|
||||
} else if (state->local_address == state->external_address) {
|
||||
spec, devolution_phone_number_for_netloc(state->data->local_address, netloc.second));
|
||||
} else if (state->data->local_address == state->data->external_address) {
|
||||
config_log.info_f(
|
||||
"Note: The Devolution phone number for {} is {} (local+external)",
|
||||
spec, devolution_phone_number_for_netloc(state->local_address, netloc.second));
|
||||
spec, devolution_phone_number_for_netloc(state->data->local_address, netloc.second));
|
||||
} else {
|
||||
config_log.info_f(
|
||||
"Note: The Devolution phone numbers for {} are {} (local) and {} (external)",
|
||||
spec,
|
||||
devolution_phone_number_for_netloc(state->local_address, netloc.second),
|
||||
devolution_phone_number_for_netloc(state->external_address, netloc.second));
|
||||
devolution_phone_number_for_netloc(state->data->local_address, netloc.second),
|
||||
devolution_phone_number_for_netloc(state->data->external_address, netloc.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!state->http_addresses.empty() || !state->http_addresses.empty()) {
|
||||
if (!state->data->http_addresses.empty()) {
|
||||
config_log.info_f("Starting HTTP server");
|
||||
state->http_server = std::make_shared<HTTPServer>(state);
|
||||
for (const auto& it : state->http_addresses) {
|
||||
for (const auto& it : state->data->http_addresses) {
|
||||
auto netloc = phosg::parse_netloc(it);
|
||||
state->http_server->listen(netloc.first, netloc.second);
|
||||
}
|
||||
@@ -4305,16 +4408,16 @@ Action a_run_server_replay_log(
|
||||
}
|
||||
|
||||
#ifndef PHOSG_WINDOWS
|
||||
if (!state->username.empty()) {
|
||||
config_log.info_f("Switching to user {}", state->username);
|
||||
drop_privileges(state->username);
|
||||
if (!state->data->username.empty()) {
|
||||
config_log.info_f("Switching to user {}", state->data->username);
|
||||
drop_privileges(state->data->username);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool should_run_shell;
|
||||
if (state->run_shell_behavior == ServerState::RunShellBehavior::DEFAULT) {
|
||||
if (state->data->run_shell_behavior == DataIndex::RunShellBehavior::DEFAULT) {
|
||||
should_run_shell = isatty(fileno(stdin));
|
||||
} else if (state->run_shell_behavior == ServerState::RunShellBehavior::ALWAYS) {
|
||||
} else if (state->data->run_shell_behavior == DataIndex::RunShellBehavior::ALWAYS) {
|
||||
should_run_shell = true;
|
||||
} else {
|
||||
should_run_shell = false;
|
||||
@@ -4331,8 +4434,19 @@ Action a_run_server_replay_log(
|
||||
state->io_context->run();
|
||||
config_log.info_f("Normal shutdown");
|
||||
|
||||
if (last_running_replay) {
|
||||
throw std::runtime_error("Replay failed");
|
||||
if (!replay_sessions.empty()) {
|
||||
size_t num_failed_replays = 0;
|
||||
for (const auto& [log_filename, replay_session] : replay_sessions) {
|
||||
if (!replay_session->failure_str().empty()) {
|
||||
config_log.warning_f("Replay failed: {}", log_filename);
|
||||
num_failed_replays++;
|
||||
}
|
||||
}
|
||||
if (num_failed_replays) {
|
||||
throw std::runtime_error(std::format("{}/{} replays failed", num_failed_replays, replay_sessions.size()));
|
||||
} else {
|
||||
config_log.info_f("All {} replays succeeded", replay_sessions.size());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
@@ -25,14 +25,14 @@ void ProxySession::set_drop_mode(
|
||||
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
|
||||
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
|
||||
this->item_creator = std::make_shared<ItemCreator>(
|
||||
s->common_item_set(version, nullptr),
|
||||
s->rare_item_set(version, nullptr),
|
||||
s->armor_random_set,
|
||||
s->tool_random_set,
|
||||
s->weapon_random_set(this->lobby_difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table(version),
|
||||
s->item_stack_limits(version),
|
||||
s->data->common_item_set(version, nullptr),
|
||||
s->data->rare_item_set(version, nullptr),
|
||||
s->data->armor_random_set,
|
||||
s->data->tool_random_set,
|
||||
s->data->weapon_random_set(this->lobby_difficulty),
|
||||
s->data->tekker_adjustment_set,
|
||||
s->data->item_parameter_table(version),
|
||||
s->data->item_stack_limits(version),
|
||||
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
|
||||
this->lobby_difficulty,
|
||||
this->lobby_section_id,
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
#include "Map.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
|
||||
struct ServerState;
|
||||
class ServerState;
|
||||
|
||||
struct ProxySession {
|
||||
bool ending_intentionally = false;
|
||||
|
||||
+2
-2
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+111
-115
@@ -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
@@ -319,19 +319,17 @@ void ReplaySession::apply_default_mask(std::shared_ptr<Event> ev) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::logic_error("invalid game version");
|
||||
throw std::logic_error("Invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
|
||||
: state(state),
|
||||
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
|
||||
commands_sent(0),
|
||||
bytes_sent(0),
|
||||
commands_received(0),
|
||||
bytes_received(0),
|
||||
idle_timeout_timer(*this->state->io_context),
|
||||
run_failed(false) {
|
||||
idle_timeout_timer(*this->state->io_context) {
|
||||
std::shared_ptr<Event> parsing_command = nullptr;
|
||||
|
||||
size_t line_num = 0;
|
||||
@@ -364,16 +362,16 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
}
|
||||
|
||||
if (line == "### use psov2 crypt") {
|
||||
this->state->use_psov2_rand_crypt = true;
|
||||
this->use_psov2_rand_crypt = true;
|
||||
}
|
||||
if (line == "### use legacy item random behavior") {
|
||||
this->state->use_legacy_item_random_behavior = true;
|
||||
this->use_legacy_item_random_behavior = true;
|
||||
}
|
||||
if (line.starts_with("### cc ")) {
|
||||
// ### cc $<chat command>
|
||||
if (this->clients.size() != 1) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
|
||||
"(ev-line {}) Bare `cc` shortcut cannot be used with multiple clients connected; use `on C-X cc` instead",
|
||||
line_num));
|
||||
}
|
||||
std::shared_ptr<Event> event;
|
||||
@@ -383,7 +381,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
event->data = encode_chat_message(c->version, line.substr(7));
|
||||
num_events++;
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Failed to generate chat message ({})", line_num, e.what()));
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -400,7 +398,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
|
||||
num_events++;
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Failed to generate chat message ({})", line_num, e.what()));
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -412,21 +410,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
|
||||
if (!tokens[8].starts_with("C-")) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Client connection message missing client ID token", line_num));
|
||||
}
|
||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||
|
||||
auto listen_tokens = phosg::split(tokens[10], '-');
|
||||
if (listen_tokens.size() < 4) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
|
||||
"(ev-line {}) Client connection message listening socket token format is incorrect", line_num));
|
||||
}
|
||||
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
|
||||
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
|
||||
|
||||
auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
|
||||
if (!this->clients.emplace(c->id, c).second) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Duplicate client ID in input log", line_num));
|
||||
}
|
||||
this->create_event(Event::Type::CONNECT, c, line_num);
|
||||
num_events++;
|
||||
@@ -438,21 +436,21 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
if (tokens.size() < 11) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Client disconnection message has incorrect token count", line_num));
|
||||
}
|
||||
if (!tokens[10].starts_with("C-")) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Client disconnection message missing client ID token", line_num));
|
||||
}
|
||||
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
|
||||
try {
|
||||
auto& c = this->clients.at(client_id);
|
||||
if (c->disconnect_event.get()) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Client has multiple disconnect events", line_num));
|
||||
}
|
||||
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
|
||||
num_events++;
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Unknown disconnecting client ID in input log", line_num));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -466,7 +464,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
if (tokens.size() < 10) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Command header line too short", line_num));
|
||||
}
|
||||
bool from_client = (tokens[6] == "Received");
|
||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||
@@ -475,7 +473,7 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
|
||||
num_events++;
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) Input log contains command for missing client", line_num));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -494,8 +492,12 @@ ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log
|
||||
}
|
||||
|
||||
asio::awaitable<void> ReplaySession::run() {
|
||||
bool prev_use_psov2_rand_crypt = this->state->use_psov2_rand_crypt;
|
||||
bool prev_use_legacy_item_random_behavior = this->state->use_legacy_item_random_behavior;
|
||||
this->state->use_psov2_rand_crypt = this->use_psov2_rand_crypt;
|
||||
this->state->use_legacy_item_random_behavior = this->use_legacy_item_random_behavior;
|
||||
|
||||
try {
|
||||
replay_log.info_f("Starting replay");
|
||||
while (this->first_event) {
|
||||
if (!this->first_event->complete) {
|
||||
auto& c = this->clients.at(this->first_event->client_id);
|
||||
@@ -505,15 +507,15 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
case Event::Type::CONNECT: {
|
||||
if (c->channel->connected()) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
|
||||
"(ev-line {}) Connect event on already-connected client", this->first_event->line_num));
|
||||
}
|
||||
|
||||
std::shared_ptr<const PortConfiguration> port_config;
|
||||
const DataIndex::PortConfiguration* port_config;
|
||||
try {
|
||||
port_config = this->state->number_to_port_config.at(c->port);
|
||||
port_config = &this->state->data->number_to_port_config.at(c->port);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
|
||||
"(ev-line {}) Client connected to port missing from configuration", this->first_event->line_num));
|
||||
}
|
||||
|
||||
auto server_channel = std::make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
||||
@@ -523,7 +525,7 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
|
||||
} else {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) no server available for connection", this->first_event->line_num));
|
||||
"(ev-line {}) No server available for connection", this->first_event->line_num));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -535,7 +537,7 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
case Event::Type::SEND:
|
||||
if (!c->channel->connected()) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
|
||||
"(ev-line {}) Send event attempted on unconnected client", this->first_event->line_num));
|
||||
}
|
||||
c->channel->send(this->first_event->data);
|
||||
this->commands_sent++;
|
||||
@@ -544,8 +546,8 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
|
||||
case Event::Type::RECEIVE: {
|
||||
if (!c->channel->connected()) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
||||
this->first_event->line_num));
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) Receive event on non-connected client", this->first_event->line_num));
|
||||
}
|
||||
if (c->receive_events.front() != this->first_event) {
|
||||
throw std::logic_error("Client receive events are out of order");
|
||||
@@ -561,25 +563,29 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
this->bytes_received += full_command.size();
|
||||
|
||||
if (c->receive_events.empty()) {
|
||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw std::runtime_error("received unexpected command for client");
|
||||
std::string data_str = phosg::format_data(full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw std::runtime_error(std::format("Received unexpected command for client:\n{}", data_str));
|
||||
}
|
||||
|
||||
auto& ev = c->receive_events.front();
|
||||
if ((full_command.size() != ev->data.size()) && !ev->allow_size_disparity) {
|
||||
replay_log.error_f("Expected command:");
|
||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
replay_log.error_f("Received command:");
|
||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw std::runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
||||
std::string expected_data = phosg::format_data(
|
||||
ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
std::string received_data = phosg::format_data(
|
||||
full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) Received command sizes do not match:\nExpected command:\n{}\nReceived command:\n{}",
|
||||
ev->line_num, expected_data, received_data));
|
||||
}
|
||||
for (size_t x = 0; x < std::min<size_t>(full_command.size(), ev->data.size()); x++) {
|
||||
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
|
||||
replay_log.error_f("Expected command:");
|
||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
replay_log.error_f("Received command:");
|
||||
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw std::runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
||||
std::string expected_data = phosg::format_data(
|
||||
ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
std::string received_data = phosg::format_data(
|
||||
full_command, 0, ev->data, phosg::FormatDataFlags::USE_COLOR | phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) Received command does not match expected\nExpected command:\n{}\nReceived command:\n{}",
|
||||
ev->line_num, expected_data, received_data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,18 +630,18 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
|
||||
// first one here.
|
||||
c->channel->crypt_in = std::make_shared<PSOBBEncryption>(
|
||||
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
|
||||
*this->state->data->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
|
||||
c->channel->crypt_out = std::make_shared<PSOBBEncryption>(
|
||||
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
|
||||
*this->state->data->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("unsupported encryption version");
|
||||
throw std::logic_error("Unsupported encryption version");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::logic_error("unhandled event type");
|
||||
throw std::logic_error("Unhandled event type");
|
||||
}
|
||||
this->first_event->complete = true;
|
||||
}
|
||||
@@ -646,13 +652,12 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
replay_log.error_f("Replay failed: {}", e.what());
|
||||
this->failure = std::format("Replay failed: {}", e.what());
|
||||
if (this->first_event) {
|
||||
replay_log.error_f("Next pending event: {}", this->first_event->str());
|
||||
this->failure += std::format("\nNext pending event: {}", this->first_event->str());
|
||||
} else {
|
||||
replay_log.error_f("No events are pending at failure time");
|
||||
this->failure += std::format("\nNo events are pending at failure time");
|
||||
}
|
||||
this->run_failed = true;
|
||||
}
|
||||
|
||||
for (auto& [_, c] : this->clients) {
|
||||
@@ -660,14 +665,18 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
c->channel->disconnect();
|
||||
}
|
||||
}
|
||||
this->state->use_psov2_rand_crypt = this->prev_psov2_crypt_enabled;
|
||||
|
||||
if (!this->run_failed) {
|
||||
this->state->use_psov2_rand_crypt = prev_use_psov2_rand_crypt;
|
||||
this->state->use_legacy_item_random_behavior = prev_use_legacy_item_random_behavior;
|
||||
|
||||
if (this->failure.empty()) {
|
||||
// Wait a bit longer to ensure that any command sent at the end of the replay session don't crash the server
|
||||
co_await async_sleep(std::chrono::seconds(2));
|
||||
replay_log.info_f("Replay complete: {} commands sent ({} bytes), {} commands received ({} bytes)",
|
||||
this->commands_sent, this->bytes_sent, this->commands_received, this->bytes_received);
|
||||
}
|
||||
|
||||
this->idle_timeout_timer.cancel();
|
||||
}
|
||||
|
||||
void ReplaySession::reschedule_idle_timeout() {
|
||||
|
||||
@@ -21,8 +21,9 @@ public:
|
||||
~ReplaySession() = default;
|
||||
|
||||
asio::awaitable<void> run();
|
||||
inline bool failed() const {
|
||||
return this->run_failed;
|
||||
|
||||
inline std::string failure_str() const {
|
||||
return this->failure;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -62,7 +63,8 @@ private:
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
bool prev_psov2_crypt_enabled;
|
||||
bool use_psov2_rand_crypt = false;
|
||||
bool use_legacy_item_random_behavior = false;
|
||||
|
||||
std::unordered_map<uint64_t, std::shared_ptr<Client>> clients;
|
||||
|
||||
@@ -75,7 +77,7 @@ private:
|
||||
size_t bytes_received;
|
||||
|
||||
asio::steady_timer idle_timeout_timer;
|
||||
bool run_failed;
|
||||
std::string failure;
|
||||
|
||||
std::shared_ptr<ReplaySession::Event> create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num);
|
||||
|
||||
|
||||
+76
-76
@@ -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 {
|
||||
|
||||
+54
-2078
File diff suppressed because it is too large
Load Diff
+16
-381
@@ -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
@@ -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>{};
|
||||
});
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -411,6 +411,7 @@ I 72448 2025-05-20 20:08:45 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.
|
||||
0010 | A6 31 06 C2 00 00 00 00 A0 4C 89 3E | 1 L >
|
||||
I 72448 2025-05-20 20:08:46 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.16.0.30:64731 (version=DC_11_2000 command=60 flag=00)
|
||||
0000 | 60 00 10 00 4C 03 00 00 0A 00 00 00 00 40 00 00 | ` L @
|
||||
### cc @variations 00020311101000212120000000000090
|
||||
I 72448 2025-05-20 20:09:02 - [Commands] Received from C-1 (Kira Lv.1) @ ip:172.16.0.30:64731 (version=DC_11_2000 command=0C flag=03)
|
||||
0000 | 0C 03 2C 00 00 00 00 00 00 00 00 00 41 41 41 41 | , AAAA
|
||||
0010 | 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | A
|
||||
|
||||
Reference in New Issue
Block a user