From 0b4d5b2f891e25ee0067f181e984e259f7423bf3 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 22 Aug 2025 22:39:32 -0700 Subject: [PATCH] add BB BankSize patch --- CMakeLists.txt | 2 +- src/ChatCommands.cc | 66 +- src/ChoiceSearch.cc | 14 +- src/Client.cc | 602 +++++++++--------- src/Client.hh | 83 ++- src/Episode3/Tournament.cc | 2 +- src/GameServer.cc | 2 +- src/HTTPServer.cc | 4 +- src/Items.cc | 6 +- src/Lobby.cc | 16 +- src/PlayerFilesManager.cc | 122 ---- src/PlayerFilesManager.hh | 48 -- src/PlayerInventory.cc | 116 ++++ src/PlayerInventory.hh | 138 ++-- src/ProxyCommands.cc | 2 +- src/ReceiveCommands.cc | 73 ++- src/ReceiveSubcommands.cc | 134 ++-- src/SendCommands.cc | 65 +- src/ServerState.cc | 6 +- src/ServerState.hh | 4 +- src/ShellCommands.cc | 5 +- .../BlueBurstExclusive/BankSize.59NL.patch.s | 102 +++ system/config.example.json | 7 + 23 files changed, 814 insertions(+), 805 deletions(-) delete mode 100644 src/PlayerFilesManager.cc delete mode 100644 src/PlayerFilesManager.hh create mode 100644 src/PlayerInventory.cc create mode 100644 system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s diff --git a/CMakeLists.txt b/CMakeLists.txt index fd45b55c..578c7f13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,7 @@ set(SOURCES src/Menu.cc src/NetworkAddresses.cc src/PatchFileIndex.cc - src/PlayerFilesManager.cc + src/PlayerInventory.cc src/PlayerSubordinates.cc src/PPKArchive.cc src/ProxyCommands.cc diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 1dddd1c3..85755bc6 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -225,7 +225,7 @@ static asio::awaitable server_command_announce_inner(const Args& a, bool m send_text_or_scrolling_message(s, a.text, a.text); } } else { - auto from_name = a.c->character()->disp.name.decode(a.c->language()); + auto from_name = a.c->character_file()->disp.name.decode(a.c->language()); if (mail) { send_simple_mail(s, 0, from_name, a.text); } else { @@ -332,7 +332,7 @@ ChatCommandDefinition cc_auction( }); static string name_for_client(shared_ptr c) { - auto player = c->character(false); + auto player = c->character_file(false); if (player.get()) { return escape_player_name(player->disp.name.decode(player->inventory.language)); } @@ -417,30 +417,24 @@ ChatCommandDefinition cc_bank( ssize_t new_char_index = a.text.empty() ? (a.c->bb_character_index + 1) : stol(a.text, nullptr, 0); - if (new_char_index == 0) { - if (a.c->use_shared_bank()) { - send_text_message(a.c, "$C6Using shared bank (0)"); - } else { - send_text_message(a.c, "$C6Created shared bank (0)"); - } + if (new_char_index <= 0) { + a.c->change_bank(-1); + send_text_message(a.c, "$C6Using shared bank"); } else if (new_char_index <= 127) { - a.c->use_character_bank(new_char_index - 1); - auto bp = a.c->current_bank_character(); - - auto name = escape_player_name(bp->disp.name.decode(a.c->language())); - send_text_message_fmt(a.c, "$C6Using {}\'s bank ({})", name, new_char_index); + a.c->change_bank(new_char_index - 1); + send_text_message_fmt(a.c, "$C6Using character {}'s bank", new_char_index); } else { throw precondition_failed("$C6Invalid bank number"); } - auto& bank = a.c->current_bank(); - bank.assign_ids(0x99000000 + (a.c->lobby_client_id << 20)); + auto bank = a.c->bank_file(); + bank->assign_ids(0x99000000 + (a.c->lobby_client_id << 20)); a.c->log.info_f("Assigned bank item IDs"); a.c->print_bank(); - send_text_message_fmt(a.c, "{} items\n{} Meseta", bank.num_items, bank.meseta); + send_text_message_fmt(a.c, "{} items\n{} Meseta", bank->items.size(), bank->meseta); co_return; }); @@ -491,7 +485,7 @@ static asio::awaitable server_command_bbchar_savechar(const Args& a, bool // server already has it) GetPlayerInfoResult ch; if (a.c->version() == Version::BB_V4) { - ch.character = a.c->character(); + ch.character = a.c->character_file(); ch.is_full_info = true; } else { ch = co_await send_get_player_info(a.c, true); @@ -501,11 +495,6 @@ static asio::awaitable server_command_bbchar_savechar(const Args& a, bool ? Client::character_filename(dest_bb_license->username, dest_character_index) : Client::backup_character_filename(dest_account->account_id, dest_character_index, is_ep3(a.c->version())); - if (s->player_files_manager->get_character(filename)) { - send_text_message(a.c, "$C6The target player\nis currently loaded.\nSign off in Blue\nBurst and try again."); - co_return; - } - if (ch.is_full_info) { // Client sent 30; ch contains the verbatim save file from the client if (ch.ep3_character) { @@ -911,7 +900,7 @@ ChatCommandDefinition cc_edit( using MatType = PSOBBCharacterFile::MaterialType; try { - auto p = a.c->character(); + auto p = a.c->character_file(); if (tokens.at(0) == "atp" && (cheats_allowed || !s->cheat_flags.edit_stats)) { p->disp.stats.char_stats.atp = stoul(tokens.at(1)); } else if (tokens.at(0) == "mst" && (cheats_allowed || !s->cheat_flags.edit_stats)) { @@ -1331,7 +1320,7 @@ ChatCommandDefinition cc_killcount( +[](const Args& a) -> asio::awaitable { a.check_is_proxy(false); - auto p = a.c->character(); + auto p = a.c->character_file(); vector item_indexes; for (size_t z = 0; z < p->inventory.num_items; z++) { const auto& item = p->inventory.items[z]; @@ -1597,13 +1586,13 @@ ChatCommandDefinition cc_loadchar( }; if (a.c->version() == Version::DC_V2) { - PSODCV2CharacterFile::Character dc_char = *a.c->character(); + PSODCV2CharacterFile::Character dc_char = *a.c->character_file(); co_await send_set_extended_player_info(dc_char); } else if (a.c->version() == Version::GC_NTE) { - PSOGCNTECharacterFileCharacter gc_char = *a.c->character(); + PSOGCNTECharacterFileCharacter gc_char = *a.c->character_file(); co_await send_set_extended_player_info(gc_char); } else if (a.c->version() == Version::GC_V3) { - PSOGCCharacterFile::Character gc_char = *a.c->character(); + PSOGCCharacterFile::Character gc_char = *a.c->character_file(); co_await send_set_extended_player_info(gc_char); } else if (a.c->version() == Version::GC_EP3_NTE) { PSOGCEp3NTECharacter nte_char = *ep3_char; @@ -1614,7 +1603,7 @@ ChatCommandDefinition cc_loadchar( if (!a.c->login || !a.c->login->xb_license) { throw runtime_error("XB client is not logged in"); } - PSOXBCharacterFile::Character xb_char = *a.c->character(); + PSOXBCharacterFile::Character xb_char = *a.c->character_file(); xb_char.guild_card.xb_user_id_high = (a.c->login->xb_license->user_id >> 32) & 0xFFFFFFFF; xb_char.guild_card.xb_user_id_low = a.c->login->xb_license->user_id & 0xFFFFFFFF; co_await send_set_extended_player_info(xb_char); @@ -1637,7 +1626,7 @@ ChatCommandDefinition cc_matcount( +[](const Args& a) -> asio::awaitable { a.check_is_proxy(false); - auto p = a.c->character(); + auto p = a.c->character_file(); if (is_v1_or_v2(a.c->version())) { send_text_message_fmt(a.c, "{} HP, {} TP", p->get_material_usage(PSOBBCharacterFile::MaterialType::HP), @@ -1879,7 +1868,7 @@ ChatCommandDefinition cc_qcheck( if (!l->quest_flags_known || l->quest_flags_known->get(l->difficulty, flag_num)) { send_text_message_fmt(a.c, "$C7Game: flag 0x{:X} ({})\nis {} on {}", flag_num, flag_num, - a.c->character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set", + a.c->character_file()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set", name_for_difficulty(l->difficulty)); } else { send_text_message_fmt(a.c, "$C7Game: flag 0x{:X} ({})\nis unknown on {}", @@ -1888,7 +1877,7 @@ ChatCommandDefinition cc_qcheck( } else if (a.c->version() == Version::BB_V4) { send_text_message_fmt(a.c, "$C7Player: flag 0x{:X} ({})\nis {} on {}", flag_num, flag_num, - a.c->character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set", + a.c->character_file()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set", name_for_difficulty(l->difficulty)); } co_return; @@ -1913,7 +1902,7 @@ static void command_qset_qclear(const Args& a, bool should_set) { } } - auto p = a.c->character(false); + auto p = a.c->character_file(false); if (p) { if (should_set) { p->quest_flags.set(l->difficulty, flag_num); @@ -1966,7 +1955,7 @@ ChatCommandDefinition cc_qfread( throw runtime_error("invalid quest counter definition"); } - uint32_t counter_value = a.c->character()->quest_counters.at(counter_index) & mask; + uint32_t counter_value = a.c->character_file()->quest_counters.at(counter_index) & mask; while (!(mask & 1)) { mask >>= 1; @@ -1986,7 +1975,7 @@ ChatCommandDefinition cc_qgread( +[](const Args& a) -> asio::awaitable { a.check_is_proxy(false); uint8_t counter_num = stoul(a.text, nullptr, 0); - const auto& counters = a.c->character()->quest_counters; + const auto& counters = a.c->character_file()->quest_counters; if (counter_num >= counters.size()) { throw precondition_failed("$C7Counter ID must be\nless than {}", counters.size()); } else { @@ -2015,11 +2004,11 @@ ChatCommandDefinition cc_qgwrite( uint8_t counter_num = stoul(tokens[0], nullptr, 0); uint32_t value = stoul(tokens[1], nullptr, 0); - auto& counters = a.c->character()->quest_counters; + auto& counters = a.c->character_file()->quest_counters; if (counter_num >= counters.size()) { throw precondition_failed("$C7Counter ID must be\nless than {}", counters.size()); } else { - a.c->character()->quest_counters[counter_num] = value; + a.c->character_file()->quest_counters[counter_num] = value; G_SetQuestCounter_BB_6xD2 cmd = {{0xD2, sizeof(G_SetQuestCounter_BB_6xD2) / 4, a.c->lobby_client_id}, counter_num, value}; send_command_t(a.c, 0x60, 0x00, cmd); send_text_message_fmt(a.c, "$C7Quest counter {}\nset to {}", counter_num, value); @@ -2534,7 +2523,7 @@ ChatCommandDefinition cc_surrender( if (!ps || !ps->is_alive()) { throw precondition_failed("$C6Defeated players\ncannot surrender"); } - string name = remove_color(a.c->character()->disp.name.decode(a.c->language())); + string name = remove_color(a.c->character_file()->disp.name.decode(a.c->language())); send_text_message_fmt(l, "$C6{} has\nsurrendered", name); for (const auto& watcher_l : l->watcher_lobbies) { send_text_message_fmt(watcher_l, "$C6{} has\nsurrendered", name); @@ -2662,6 +2651,7 @@ ChatCommandDefinition cc_switchchar( a.c->save_and_unload_character(); a.c->bb_character_index = index; + a.c->bb_bank_character_index = index; // TODO: This can trigger a client bug where the previous character's // name label object isn't deleted if the leave and join notifications @@ -2952,7 +2942,7 @@ ChatCommandDefinition cc_where( if (!a.c->proxy_session && l && l->is_game()) { for (auto lc : l->clients) { if (lc && (lc != a.c)) { - string name = lc->character()->disp.name.decode(lc->language()); + string name = lc->character_file()->disp.name.decode(lc->language()); send_text_message_fmt(a.c, "$C6{}$C7 {:X}:{}", name, lc->floor, FloorDefinition::get(l->episode, lc->floor).short_name); } diff --git a/src/ChoiceSearch.cc b/src/ChoiceSearch.cc index 4b52cffe..105457c1 100644 --- a/src/ChoiceSearch.cc +++ b/src/ChoiceSearch.cc @@ -28,10 +28,10 @@ const vector CHOICE_SEARCH_CATEGORIES({ if (choice_id == 0x0000) { return true; } - uint32_t target_level = target_c->character()->disp.stats.level + 1; + uint32_t target_level = target_c->character_file()->disp.stats.level + 1; switch (choice_id) { case 0x0001: - return (labs(static_cast(target_level - searcher_c->character()->disp.stats.level)) <= 5); + return (labs(static_cast(target_level - searcher_c->character_file()->disp.stats.level)) <= 5); case 0x0002: return (target_level <= 10); case 0x0003: @@ -80,13 +80,13 @@ const vector CHOICE_SEARCH_CATEGORIES({ case 0x0000: return true; case 0x0010: - return target_c->character()->disp.visual.class_flags & 0x20; + return target_c->character_file()->disp.visual.class_flags & 0x20; case 0x0011: - return target_c->character()->disp.visual.class_flags & 0x40; + return target_c->character_file()->disp.visual.class_flags & 0x40; case 0x0012: - return target_c->character()->disp.visual.class_flags & 0x80; + return target_c->character_file()->disp.visual.class_flags & 0x80; default: - return ((choice_id - 1) == target_c->character()->disp.visual.char_class); + return ((choice_id - 1) == target_c->character_file()->disp.visual.char_class); } }, }, @@ -143,7 +143,7 @@ const vector CHOICE_SEARCH_CATEGORIES({ {0x0006, "Challenge"}, }, .client_matches = +[](shared_ptr, shared_ptr target_c, uint16_t choice_id) -> bool { - uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204); + uint16_t target_choice_id = target_c->character_file()->choice_search_config.get_setting(0x0204); return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id); }, }, diff --git a/src/Client.cc b/src/Client.cc index fdecf5a6..8491990c 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -244,12 +244,11 @@ Client::~Client() { void Client::update_channel_name() { string default_name = this->channel->default_name(); - auto player = this->character(false, false); + auto player = this->character_file(false, false); if (player) { string name_str = player->disp.name.decode(this->language()); size_t level = player->disp.stats.level + 1; - this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", - this->id, name_str, level, default_name); + this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", this->id, name_str, level, default_name); } else { this->channel->name = std::format("C-{:X} @ {}", this->id, default_name); } @@ -263,7 +262,7 @@ void Client::reschedule_save_game_data_timer() { this->save_game_data_timer.expires_after(std::chrono::seconds(60)); this->save_game_data_timer.async_wait([this](std::error_code ec) { if (!ec) { - if (this->character(false)) { + if (this->character_file(false)) { this->save_all(); } this->reschedule_save_game_data_timer(); @@ -336,7 +335,7 @@ shared_ptr Client::team() const { return nullptr; } - auto p = this->character(false); + auto p = this->character_file(false); auto s = this->require_server_state(); auto team = s->team_index->get_by_id(this->login->account->bb_team_id); if (!team) { @@ -384,7 +383,7 @@ bool Client::evaluate_quest_availability_expression( if (game && !game->quest_flag_values) { throw logic_error("quest flags are missing from game"); } - auto p = this->character(); + auto p = this->character_file(); IntegralExpression::Env env = { .flags = &p->quest_flags.data.at(difficulty), .challenge_records = &p->challenge_records, @@ -448,8 +447,184 @@ void Client::set_login(shared_ptr login) { } } +// System file + +string Client::system_filename(const string& bb_username) { + return std::format("system/players/system_{}.psosys", bb_username); +} + +string Client::system_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have system data"); + } + if (!this->login || !this->login->bb_license) { + throw logic_error("client is not logged in"); + } + return this->system_filename(this->login->bb_license->username); +} + +shared_ptr Client::system_file(bool allow_load) { + if (!this->system_data && allow_load) { + this->load_all_files(); + } + return this->system_data; +} + +shared_ptr Client::system_file(bool throw_if_missing) const { + if (!this->system_data.get() && throw_if_missing) { + throw runtime_error("system file is not loaded"); + } + return this->system_data; +} + +void Client::save_system_file() const { + if (!this->system_data) { + throw logic_error("no system file loaded"); + } + string filename = this->system_filename(); + phosg::save_object_file(filename, *this->system_data); + this->log.info_f("Saved system file {}", filename); +} + +// Guild Card file + +string Client::guild_card_filename(const string& bb_username) { + return std::format("system/players/guild_cards_{}.psocard", bb_username); +} + +string Client::guild_card_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have saved character data"); + } + if (!this->login || !this->login->bb_license) { + throw logic_error("client is not logged in"); + } + return this->guild_card_filename(this->login->bb_license->username); +} + +shared_ptr Client::guild_card_file(bool allow_load) { + if (!this->guild_card_data && allow_load) { + this->load_all_files(); + } + return this->guild_card_data; +} + +shared_ptr Client::guild_card_file(bool allow_load) const { + if (!this->guild_card_data && allow_load) { + throw runtime_error("account data is not loaded"); + } + return this->guild_card_data; +} + +void Client::save_guild_card_file() const { + if (!this->guild_card_data.get()) { + throw logic_error("no Guild Card file loaded"); + } + string filename = this->guild_card_filename(); + phosg::save_object_file(filename, *this->guild_card_data); + this->log.info_f("Saved Guild Card file {}", filename); +} + +// Character file + +string Client::character_filename(const std::string& bb_username, ssize_t index) { + if (bb_username.empty()) { + throw logic_error("non-BB players do not have saved character data"); + } + if (index < 0) { + throw logic_error("character index is not set"); + } + return std::format("system/players/player_{}_{}.psochar", bb_username, index); +} + +string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) { + return std::format("system/players/backup_player_{}_{}.{}", + account_id, index, is_ep3 ? "pso3char" : "psochar"); +} + +string Client::character_filename() const { + if (this->version() != Version::BB_V4) { + throw logic_error("non-BB players do not have saved character data"); + } + if (!this->login || !this->login->bb_license) { + throw logic_error("client is not logged in"); + } + return this->character_filename(this->login->bb_license->username, this->bb_character_index); +} + +shared_ptr Client::character_file(bool allow_load, bool allow_overlay) { + if (this->overlay_character_data && allow_overlay) { + return this->overlay_character_data; + } + if (!this->character_data && allow_load) { + if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) { + throw runtime_error("character index not specified"); + } + this->load_all_files(); + } + return this->character_data; +} + +shared_ptr Client::character_file(bool throw_if_missing, bool allow_overlay) const { + if (allow_overlay && this->overlay_character_data) { + return this->overlay_character_data; + } + if (!this->character_data && throw_if_missing) { + throw runtime_error("character data is not loaded"); + } + return this->character_data; +} + +void Client::save_character_file( + const string& filename, + shared_ptr system, + shared_ptr character) { + PSOCHARFile::save(filename, system, character); +} + +void Client::save_ep3_character_file( + const string& filename, + const PSOGCEp3CharacterFile::Character& character) { + phosg::save_file(filename, &character, sizeof(character)); +} + +void Client::save_character_file() { + if (!this->system_data.get()) { + throw logic_error("no system file loaded"); + } + if (!this->character_data.get()) { + throw logic_error("no character file loaded"); + } + if (this->should_update_play_time) { + // This is slightly inaccurate, since fractions of a second are truncated + // off each time we save. I'm lazy, so insert shrug emoji here. + uint64_t t = phosg::now(); + uint64_t seconds = (t - this->last_play_time_update) / 1000000; + this->character_data->play_time_seconds += seconds; + this->log.info_f("Added {} seconds to play time", seconds); + this->last_play_time_update = t; + if (this->bank_data && (this->bb_bank_character_index == this->bb_character_index)) { + this->character_data->bank = *this->bank_data; + this->log.info_f("Committed bank data back to character file"); + } + } + + auto filename = this->character_filename(); + this->save_character_file(filename, this->system_data, this->character_data); + this->log.info_f("Saved character file {}", filename); +} + +void Client::create_character_file( + uint32_t guild_card_number, + uint8_t language, + const PlayerDispDataBBPreview& preview, + shared_ptr level_table) { + this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table); + this->save_character_file(); +} + void Client::create_battle_overlay(shared_ptr rules, shared_ptr level_table) { - this->overlay_character_data = make_shared(*this->character(true, false)); + this->overlay_character_data = make_shared(*this->character_file(true, false)); if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) { this->overlay_character_data->inventory.remove_all_items_of_type(0); @@ -499,7 +674,7 @@ void Client::create_battle_overlay(shared_ptr rules, shared_p } void Client::create_challenge_overlay(Version version, size_t template_index, shared_ptr level_table) { - auto p = this->character(true, false); + auto p = this->character_file(true, false); const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index); this->overlay_character_data = make_shared(*p); @@ -543,124 +718,109 @@ void Client::create_challenge_overlay(Version version, size_t template_index, sh } } -void Client::import_blocked_senders(const parray& blocked_senders) { - this->blocked_senders.clear(); - for (size_t z = 0; z < blocked_senders.size(); z++) { - if (blocked_senders[z]) { - this->blocked_senders.emplace(blocked_senders[z]); - } - } -} +// Bank file -shared_ptr Client::system_file(bool allow_load) { - if (!this->system_data && allow_load) { - this->load_all_files(); - } - return this->system_data; -} - -shared_ptr Client::system_file(bool allow_load) const { - if (!this->system_data.get() && allow_load) { - throw runtime_error("system data is not loaded"); - } - return this->system_data; -} - -shared_ptr Client::character(bool allow_load, bool allow_overlay) { - if (this->overlay_character_data && allow_overlay) { - return this->overlay_character_data; - } - if (!this->character_data && allow_load) { - if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) { - throw runtime_error("character index not specified"); - } - this->load_all_files(); - } - return this->character_data; -} - -shared_ptr Client::character(bool allow_load, bool allow_overlay) const { - if (allow_overlay && this->overlay_character_data) { - return this->overlay_character_data; - } - if (!this->character_data && allow_load) { - throw runtime_error("character data is not loaded"); - } - return this->character_data; -} - -shared_ptr Client::guild_card_file(bool allow_load) { - if (!this->guild_card_data && allow_load) { - this->load_all_files(); - } - return this->guild_card_data; -} - -shared_ptr Client::guild_card_file(bool allow_load) const { - if (!this->guild_card_data && allow_load) { - throw runtime_error("account data is not loaded"); - } - return this->guild_card_data; -} - -string Client::system_filename() const { - if (this->version() != Version::BB_V4) { - throw logic_error("non-BB players do not have system data"); - } - if (!this->login || !this->login->bb_license) { - throw logic_error("client is not logged in"); - } - return std::format("system/players/system_{}.psosys", this->login->bb_license->username); -} - -string Client::character_filename(const std::string& bb_username, ssize_t index) { +string Client::bank_filename(const std::string& bb_username, ssize_t index) { if (bb_username.empty()) { - throw logic_error("non-BB players do not have character data"); + throw logic_error("non-BB players do not have saved character data"); } if (index < 0) { - throw logic_error("character index is not set"); + return std::format("system/players/shared_bank_{}.psobank", bb_username); + } else { + return std::format("system/players/player_{}_{}.psobank", bb_username, index); } - return std::format("system/players/player_{}_{}.psochar", bb_username, index); } -string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) { - return std::format("system/players/backup_player_{}_{}.{}", - account_id, index, is_ep3 ? "pso3char" : "psochar"); -} - -string Client::character_filename(ssize_t index) const { +string Client::bank_filename() const { if (this->version() != Version::BB_V4) { - throw logic_error("non-BB players do not have character data"); + throw logic_error("non-BB players do not have saved character data"); } if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } - return this->character_filename(this->login->bb_license->username, (index < 0) ? this->bb_character_index : index); + return this->bank_filename(this->login->bb_license->username, this->bb_bank_character_index); } -string Client::guild_card_filename() const { +std::shared_ptr Client::bank_file(bool allow_load) { if (this->version() != Version::BB_V4) { - throw logic_error("non-BB players do not have character data"); + throw logic_error("non-BB players do not have saved character data"); } - if (!this->login || !this->login->bb_license) { - throw logic_error("client is not logged in"); + if (this->has_overlay()) { + throw std::runtime_error("bank is inaccessible when overlay is present"); } - return std::format("system/players/guild_cards_{}.psocard", this->login->bb_license->username); + if (!this->bank_data && allow_load) { + try { + // If there's a psobank file, load it and ignore the character file bank + auto filename = this->bank_filename(); + auto f = phosg::fopen_unique(filename, "rb"); + this->bank_data = make_shared(); + this->bank_data->load(f.get()); + this->log.info_f("Loaded bank data from {}", filename); + } catch (const phosg::cannot_open_file&) { + // If there isn't a psobank file, use the loaded character data if the + // bank character index matches the current character index (that is, we + // should use the current character's bank); otherwise, load the + // corresponding character and parse the bank from that character file + if (this->bb_bank_character_index == this->bb_character_index) { + this->bank_data = std::make_shared(this->character_file(false, false)->bank); + this->log.info_f("Using bank data from loaded character"); + } else { + if (!this->login || !this->login->bb_license) { + throw logic_error("client is not logged in"); + } + string filename = this->character_filename(this->login->bb_license->username, this->bb_bank_character_index); + auto character = PSOCHARFile::load_shared(filename, false).character_file; + this->bank_data = std::make_shared(character->bank); + this->log.info_f("Using bank data from {}", filename); + } + } + + 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; + } + return this->bank_data; } -string Client::shared_bank_filename() const { - if (this->version() != Version::BB_V4) { - throw logic_error("non-BB players do not have character data"); +std::shared_ptr Client::bank_file(bool throw_if_missing) const { + if (!this->bank_data && throw_if_missing) { + throw std::runtime_error("bank is not loaded"); } - if (!this->login || !this->login->bb_license) { - throw logic_error("client is not logged in"); - } - return std::format("system/players/shared_bank_{}.psobank", this->login->bb_license->username); + return this->bank_data; } +void Client::save_bank_file(const string& filename, const PlayerBank& bank) { + auto f = phosg::fopen_unique(filename, "wb"); + bank.save(f.get()); +} + +void Client::save_bank_file() const { + if (!this->bank_data) { + throw logic_error("no bank file loaded"); + } + auto filename = this->bank_filename(); + this->save_bank_file(filename, *this->bank_data); + this->log.info_f("Saved bank file {}", filename); +} + +void Client::change_bank(ssize_t index) { + if (this->bank_data) { + this->save_bank_file(); + this->bank_data.reset(); + if (this->bb_bank_character_index < 0) { + this->log.info_f("Unloaded shared bank"); + } else { + this->log.info_f("Unloaded bank from character {}", this->bb_bank_character_index); + } + } + this->bb_bank_character_index = index; +} + +// Legacy files + string Client::legacy_account_filename() const { if (this->version() != Version::BB_V4) { - throw logic_error("non-BB players do not have character data"); + throw logic_error("non-BB players do not have saved character data"); } if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); @@ -670,7 +830,7 @@ string Client::legacy_account_filename() const { string Client::legacy_player_filename() const { if (this->version() != Version::BB_V4) { - throw logic_error("non-BB players do not have character data"); + throw logic_error("non-BB players do not have saved character data"); } if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); @@ -684,13 +844,13 @@ string Client::legacy_player_filename() const { static_cast(this->bb_character_index + 1)); } -void Client::create_character_file( - uint32_t guild_card_number, - uint8_t language, - const PlayerDispDataBBPreview& preview, - shared_ptr level_table) { - this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table); - this->save_character_file(); +void Client::import_blocked_senders(const parray& blocked_senders) { + this->blocked_senders.clear(); + for (size_t z = 0; z < blocked_senders.size(); z++) { + if (blocked_senders[z]) { + this->blocked_senders.emplace(blocked_senders[z]); + } + } } void Client::load_all_files() { @@ -698,6 +858,7 @@ void Client::load_all_files() { this->system_data = make_shared(); this->character_data = make_shared(); this->guild_card_data = make_shared(); + this->bank_data = make_shared(); return; } if (!this->login || !this->login->bb_license) { @@ -707,31 +868,22 @@ void Client::load_all_files() { this->system_data.reset(); this->character_data.reset(); this->guild_card_data.reset(); - - auto files_manager = this->require_server_state()->player_files_manager; + this->bank_data.reset(); string sys_filename = this->system_filename(); - this->system_data = files_manager->get_system(sys_filename); - if (this->system_data) { - player_data_log.info_f("Using loaded system file {}", sys_filename); - } else if (std::filesystem::is_regular_file(sys_filename)) { + if (std::filesystem::is_regular_file(sys_filename)) { this->system_data = make_shared(phosg::load_object_file(sys_filename, true)); - files_manager->set_system(sys_filename, this->system_data); - player_data_log.info_f("Loaded system data from {}", sys_filename); + this->log.info_f("Loaded system data from {}", sys_filename); } else { - player_data_log.info_f("System file is missing: {}", sys_filename); + this->log.info_f("System file is missing: {}", sys_filename); } if (this->bb_character_index >= 0) { string char_filename = this->character_filename(); - this->character_data = files_manager->get_character(char_filename); - if (this->character_data) { - player_data_log.info_f("Using loaded character file {}", char_filename); - } else if (std::filesystem::is_regular_file(char_filename)) { + if (std::filesystem::is_regular_file(char_filename)) { auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data); this->character_data = psochar.character_file; - files_manager->set_character(char_filename, this->character_data); - player_data_log.info_f("Loaded character data from {}", char_filename); + this->log.info_f("Loaded character data from {}", char_filename); // If there was no .psosys file, use the system file from the .psochar // file instead @@ -740,28 +892,23 @@ void Client::load_all_files() { throw logic_error("account system data not present, and also not loaded from psochar file"); } this->system_data = psochar.system_file; - files_manager->set_system(sys_filename, this->system_data); - player_data_log.info_f("Loaded system data from {}", char_filename); + this->log.info_f("Loaded system data from {}", char_filename); } this->update_character_data_after_load(this->character_data); this->system_data->language = this->language(); } else { - player_data_log.info_f("Character file is missing: {}", char_filename); + this->log.info_f("Character file is missing: {}", char_filename); } } string card_filename = this->guild_card_filename(); - this->guild_card_data = files_manager->get_guild_card(card_filename); - if (this->guild_card_data) { - player_data_log.info_f("Using loaded Guild Card file {}", card_filename); - } else if (std::filesystem::is_regular_file(card_filename)) { + if (std::filesystem::is_regular_file(card_filename)) { this->guild_card_data = make_shared(phosg::load_object_file(card_filename)); - files_manager->set_guild_card(card_filename, this->guild_card_data); - player_data_log.info_f("Loaded Guild Card data from {}", card_filename); + this->log.info_f("Loaded Guild Card data from {}", card_filename); } else { - player_data_log.info_f("Guild Card file is missing: {}", card_filename); + this->log.info_f("Guild Card file is missing: {}", card_filename); } // If any of the above files were missing, try to load from .nsa/.nsc files instead @@ -775,13 +922,11 @@ void Client::load_all_files() { } if (!this->system_data) { this->system_data = make_shared(nsa_data->system_file); - files_manager->set_system(sys_filename, this->system_data); - player_data_log.info_f("Loaded legacy system data from {}", nsa_filename); + this->log.info_f("Loaded legacy system data from {}", nsa_filename); } if (!this->guild_card_data) { this->guild_card_data = make_shared(nsa_data->guild_card_file); - files_manager->set_guild_card(card_filename, this->guild_card_data); - player_data_log.info_f("Loaded legacy Guild Card data from {}", nsa_filename); + this->log.info_f("Loaded legacy Guild Card data from {}", nsa_filename); } } @@ -794,13 +939,11 @@ void Client::load_all_files() { if (s->bb_default_joystick_config) { this->system_data->joystick_config = *s->bb_default_joystick_config; } - files_manager->set_system(sys_filename, this->system_data); - player_data_log.info_f("Created new system data"); + this->log.info_f("Created new system data"); } if (!this->guild_card_data) { this->guild_card_data = make_shared(); - files_manager->set_guild_card(card_filename, this->guild_card_data); - player_data_log.info_f("Created new Guild Card data"); + this->log.info_f("Created new Guild Card data"); } if (!this->character_data && (this->bb_character_index >= 0)) { @@ -817,7 +960,6 @@ void Client::load_all_files() { } this->character_data = make_shared(); - files_manager->set_character(this->character_filename(), this->character_data); this->character_data->inventory = nsc_data.inventory; this->character_data->disp = nsc_data.disp; this->character_data->play_time_seconds = 0; @@ -841,14 +983,20 @@ void Client::load_all_files() { this->character_data->option_flags = nsa_data->option_flags; this->character_data->symbol_chats = nsa_data->symbol_chats; this->character_data->shortcuts = nsa_data->shortcuts; - player_data_log.info_f("Loaded legacy player data from {} and {}", nsa_filename, nsc_filename); + this->log.info_f("Loaded legacy player data from {} and {}", nsa_filename, nsc_filename); } else { - player_data_log.info_f("Loaded legacy player data from {}", nsc_filename); + this->log.info_f("Loaded legacy player data from {}", nsc_filename); } this->update_character_data_after_load(this->character_data); } } + auto s = this->require_server_state(); + auto stack_limits = s->item_stack_limits(this->version()); + + // bank_file() loads the bank data + this->bank_file()->enforce_stack_limits(stack_limits); + this->blocked_senders.clear(); for (size_t z = 0; z < this->guild_card_data->blocked.size(); z++) { if (this->guild_card_data->blocked[z].present) { @@ -860,11 +1008,7 @@ void Client::load_all_files() { // Clear legacy play_time field this->character_data->disp.name.clear_after_bytes(0x18); - // Enforce item stack limits, in case they've changed - auto s = this->require_server_state(); - auto stack_limits = s->item_stack_limits(this->version()); this->character_data->inventory.enforce_stack_limits(stack_limits); - this->character_data->bank.enforce_stack_limits(stack_limits); this->login->account->auto_reply_message = this->character_data->auto_reply.decode(); this->login->account->save(); @@ -876,7 +1020,7 @@ void Client::update_character_data_after_load(shared_ptr cha charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version())); uint8_t lang = this->language(); - player_data_log.info_f("Overriding language fields in save files with {:02X} ({})", lang, char_for_language_code(lang)); + this->log.info_f("Overriding language fields in save files with {:02X} ({})", lang, char_for_language_code(lang)); charfile->inventory.language = lang; charfile->guild_card.language = lang; } @@ -891,70 +1035,9 @@ void Client::save_all() { if (this->guild_card_data) { this->save_guild_card_file(); } - if (this->external_bank) { - string filename = this->shared_bank_filename(); - phosg::save_object_file(filename, *this->external_bank); - player_data_log.info_f("Saved shared bank file {}", filename); + if (this->bank_data) { + this->save_bank_file(); } - if (this->external_bank_character) { - this->save_character_file( - this->character_filename(this->external_bank_character_index), - this->system_data, - this->external_bank_character); - } -} - -void Client::save_system_file() const { - if (!this->system_data) { - throw logic_error("no system file loaded"); - } - string filename = this->system_filename(); - phosg::save_object_file(filename, *this->system_data); - player_data_log.info_f("Saved system file {}", filename); -} - -void Client::save_character_file( - const string& filename, - shared_ptr system, - shared_ptr character) { - PSOCHARFile::save(filename, system, character); - player_data_log.info_f("Saved character file {}", filename); -} - -void Client::save_ep3_character_file( - const string& filename, - const PSOGCEp3CharacterFile::Character& character) { - phosg::save_file(filename, &character, sizeof(character)); - player_data_log.info_f("Saved Episode 3 character file {}", filename); -} - -void Client::save_character_file() { - if (!this->system_data.get()) { - throw logic_error("no system file loaded"); - } - if (!this->character_data.get()) { - throw logic_error("no character file loaded"); - } - if (this->should_update_play_time) { - // This is slightly inaccurate, since fractions of a second are truncated - // off each time we save. I'm lazy, so insert shrug emoji here. - uint64_t t = phosg::now(); - uint64_t seconds = (t - this->last_play_time_update) / 1000000; - this->character_data->play_time_seconds += seconds; - player_data_log.info_f("Added {} seconds to play time", seconds); - this->last_play_time_update = t; - } - - this->save_character_file(this->character_filename(), this->system_data, this->character_data); -} - -void Client::save_guild_card_file() const { - if (!this->guild_card_data.get()) { - throw logic_error("no Guild Card file loaded"); - } - string filename = this->guild_card_filename(); - phosg::save_object_file(filename, *this->guild_card_data); - player_data_log.info_f("Saved Guild Card file {}", filename); } void Client::load_backup_character(uint32_t account_id, size_t index) { @@ -979,88 +1062,17 @@ void Client::save_and_unload_character() { this->save_character_file(); this->character_data.reset(); this->log.info_f("Unloaded character"); - } -} - -PlayerBank200& Client::current_bank() { - if (this->external_bank) { - return *this->external_bank; - } else if (this->external_bank_character) { - return this->external_bank_character->bank; - } - return this->character()->bank; -} - -const PlayerBank200& Client::current_bank() const { - return const_cast(this)->current_bank(); -} - -std::shared_ptr Client::current_bank_character() { - return this->external_bank_character ? this->external_bank_character : this->character(); -} - -void Client::use_default_bank() { - if (this->external_bank) { - string filename = this->shared_bank_filename(); - phosg::save_object_file(filename, *this->external_bank); - this->external_bank.reset(); - player_data_log.info_f("Detached shared bank {}", filename); - } - if (this->external_bank_character) { - string filename = this->character_filename(this->external_bank_character_index); - this->save_character_file(filename, this->system_data, this->external_bank_character); - this->external_bank_character.reset(); - player_data_log.info_f("Detached character {} from bank", filename); - } -} - -bool Client::use_shared_bank() { - this->use_default_bank(); - - string filename = this->shared_bank_filename(); - auto files_manager = this->require_server_state()->player_files_manager; - this->external_bank = files_manager->get_bank(filename); - if (this->external_bank) { - player_data_log.info_f("Using loaded shared bank {}", filename); - return true; - } else if (std::filesystem::is_regular_file(filename)) { - this->external_bank = make_shared(phosg::load_object_file(filename)); - files_manager->set_bank(filename, this->external_bank); - player_data_log.info_f("Loaded shared bank {}", filename); - return true; - } else { - this->external_bank = make_shared(); - files_manager->set_bank(filename, this->external_bank); - player_data_log.info_f("Created shared bank for {}", filename); - return false; - } -} - -void Client::use_character_bank(ssize_t index) { - this->use_default_bank(); - if (index != this->bb_character_index) { - auto files_manager = this->require_server_state()->player_files_manager; - - string filename = this->character_filename(index); - this->external_bank_character = files_manager->get_character(filename); - if (this->external_bank_character) { - this->external_bank_character_index = index; - player_data_log.info_f("Using loaded character file {} for external bank", filename); - } else if (std::filesystem::is_regular_file(filename)) { - this->external_bank_character = PSOCHARFile::load_shared(filename, false).character_file; - this->update_character_data_after_load(this->external_bank_character); - this->external_bank_character_index = index; - files_manager->set_character(filename, this->external_bank_character); - player_data_log.info_f("Loaded character data from {} for external bank", filename); - } else { - throw runtime_error("character does not exist"); + if (this->bank_data) { + this->save_bank_file(); + this->bank_data.reset(); + this->log.info_f("Unloaded bank"); } } } void Client::print_inventory() const { auto s = this->require_server_state(); - auto p = this->character(); + auto p = this->character_file(); this->log.info_f("[PlayerInventory] Meseta: {}", p->disp.stats.meseta); this->log.info_f("[PlayerInventory] {} items", p->inventory.num_items); for (size_t x = 0; x < p->inventory.num_items; x++) { @@ -1073,11 +1085,11 @@ void Client::print_inventory() const { void Client::print_bank() const { auto s = this->require_server_state(); - auto bank = this->current_bank(); - this->log.info_f("[PlayerBank] Meseta: {}", bank.meseta); - this->log.info_f("[PlayerBank] {} items", bank.num_items); - for (size_t x = 0; x < bank.num_items; x++) { - const auto& item = bank.items[x]; + auto bank = this->bank_file(); + this->log.info_f("[PlayerBank] Meseta: {}", bank->meseta); + this->log.info_f("[PlayerBank] {} items", bank->items.size()); + for (size_t x = 0; x < bank->items.size(); x++) { + const auto& item = bank->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); diff --git a/src/Client.hh b/src/Client.hh index 866847f9..eea43573 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -114,6 +114,7 @@ public: uint8_t bb_client_code = 0; uint8_t bb_connection_phase = 0xFF; ssize_t bb_character_index = -1; // -1 = not set + ssize_t bb_bank_character_index = -1; // -1 = shared bank uint32_t bb_security_token = 0; parray bb_client_config; std::string login_character_name; @@ -288,6 +289,36 @@ public: void set_login(std::shared_ptr login); + void import_blocked_senders(const parray& blocked_senders); + + static std::string system_filename(const std::string& bb_username); + std::string system_filename() const; + std::shared_ptr system_file(bool allow_load = true); + std::shared_ptr system_file(bool throw_if_missing = true) const; + void save_system_file() const; + + static std::string guild_card_filename(const std::string& bb_username); + std::string guild_card_filename() const; + std::shared_ptr guild_card_file(bool allow_load = true); + std::shared_ptr guild_card_file(bool allow_load = true) const; + void save_guild_card_file() const; + + static std::string character_filename(const std::string& bb_username, ssize_t index); + static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3); + std::string character_filename() const; + std::shared_ptr character_file(bool allow_load = true, bool allow_overlay = true); + std::shared_ptr character_file(bool throw_if_missing = true, bool allow_overlay = true) const; + static void save_character_file( + const std::string& filename, + std::shared_ptr sys, + std::shared_ptr character); + static void save_ep3_character_file(const std::string& filename, const PSOGCEp3CharacterFile::Character& character); + void save_character_file(); + void create_character_file( + uint32_t guild_card_number, + uint8_t language, + const PlayerDispDataBBPreview& preview, + std::shared_ptr level_table); void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr level_table); inline void delete_overlay() { @@ -297,55 +328,23 @@ public: return this->overlay_character_data.get() != nullptr; } - void import_blocked_senders(const parray& blocked_senders); + static std::string bank_filename(const std::string& bb_username, ssize_t index); + std::string bank_filename() const; + std::shared_ptr bank_file(bool allow_load = true); + std::shared_ptr bank_file(bool throw_if_missing = true) const; + static void save_bank_file(const std::string& filename, const PlayerBank& bank); + void save_bank_file() const; + void change_bank(ssize_t bb_character_index); // -1 = use shared bank - std::shared_ptr system_file(bool allow_load = true); - std::shared_ptr character(bool allow_load = true, bool allow_overlay = true); - std::shared_ptr guild_card_file(bool allow_load = true); - std::shared_ptr system_file(bool allow_load = true) const; - std::shared_ptr character(bool allow_load = true, bool allow_overlay = true) const; - std::shared_ptr guild_card_file(bool allow_load = true) const; - - void create_character_file( - uint32_t guild_card_number, - uint8_t language, - const PlayerDispDataBBPreview& preview, - std::shared_ptr level_table); - - std::string system_filename() const; - static std::string character_filename(const std::string& bb_username, ssize_t index); - static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3); - std::string character_filename(ssize_t index = -1) const; - std::string guild_card_filename() const; - std::string shared_bank_filename() const; - - std::string legacy_player_filename() const; std::string legacy_account_filename() const; + std::string legacy_player_filename() const; void save_all(); - void save_system_file() const; - static void save_character_file( - const std::string& filename, - std::shared_ptr sys, - std::shared_ptr character); - static void save_ep3_character_file( - const std::string& filename, - const PSOGCEp3CharacterFile::Character& character); - // Note: This function is not const because it updates the player's play time. - void save_character_file(); - void save_guild_card_file() const; void load_backup_character(uint32_t account_id, size_t index); std::shared_ptr load_ep3_backup_character(uint32_t account_id, size_t index); void save_and_unload_character(); - PlayerBank200& current_bank(); - const PlayerBank200& current_bank() const; - std::shared_ptr current_bank_character(); - bool use_shared_bank(); // Returns true if the bank exists; false if it was created - void use_character_bank(ssize_t bb_character_index); - void use_default_bank(); - void print_inventory() const; void print_bank() const; @@ -359,9 +358,7 @@ private: std::shared_ptr overlay_character_data; std::shared_ptr character_data; std::shared_ptr guild_card_data; - std::shared_ptr external_bank; - std::shared_ptr external_bank_character; - ssize_t external_bank_character_index = -1; + std::shared_ptr bank_data; uint64_t last_play_time_update = 0; void load_all_files(); diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index 0cb22b85..ed00e17c 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -18,7 +18,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const string& player_n Tournament::PlayerEntry::PlayerEntry(shared_ptr c) : account_id(c->login->account->account_id), client(c), - player_name(c->character()->disp.name.decode(c->language())) {} + player_name(c->character_file()->disp.name.decode(c->language())) {} Tournament::PlayerEntry::PlayerEntry( shared_ptr com_deck) diff --git a/src/GameServer.cc b/src/GameServer.cc index 719995bb..c606c433 100644 --- a/src/GameServer.cc +++ b/src/GameServer.cc @@ -96,7 +96,7 @@ vector> GameServer::get_clients_by_identifier(const string& i continue; } - auto p = c->character(false, false); + auto p = c->character_file(false, false); if (p && p->disp.name.eq(ident, p->inventory.language)) { results.emplace_back(c); continue; diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 63543dbf..c9ab4d36 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -169,7 +169,7 @@ std::shared_ptr HTTPServer::generate_client_json( if (c->version() == Version::BB_V4) { ret->emplace("BBCharacterIndex", c->bb_character_index); } - auto p = c->character(false, false); + auto p = c->character_file(false, false); if (p) { if (!is_ep3(c->version())) { if (c->version() != Version::DC_NTE) { @@ -596,7 +596,7 @@ std::shared_ptr HTTPServer::generate_lobbies_json() const { std::shared_ptr HTTPServer::generate_summary_json() const { auto clients_json = phosg::JSON::list(); for (const auto& c : this->state->game_server->all_clients()) { - auto p = c->character(false, false); + auto p = c->character_file(false, false); auto l = c->lobby.lock(); clients_json.emplace_back(phosg::JSON::dict({ {"ID", c->id}, diff --git a/src/Items.cc b/src/Items.cc index e840a997..9c1d36a7 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -16,7 +16,7 @@ void player_use_item(shared_ptr c, size_t item_index, shared_ptrversion()) || is_v4; bool should_delete_item = is_v3_or_later; - auto player = c->character(); + auto player = c->character_file(); auto& item = player->inventory.items[item_index]; uint32_t primary_identifier = item.data.primary_identifier(); @@ -47,7 +47,7 @@ void player_use_item(shared_ptr c, size_t item_index, shared_ptr(weapon.data.data1[3] + item.data.data1[2] + 1, weapon_def.max_grind); } else if ((primary_identifier & 0xFFFF0000) == 0x030B0000) { // Material - auto p = c->character(); + auto p = c->character_file(); using Type = PSOBBCharacterFile::MaterialType; Type type; @@ -499,7 +499,7 @@ void apply_mag_feed_result( void player_feed_mag(std::shared_ptr c, size_t mag_item_index, size_t fed_item_index) { auto s = c->require_server_state(); - auto player = c->character(); + auto player = c->character_file(); apply_mag_feed_result( player->inventory.items[mag_item_index].data, player->inventory.items[fed_item_index].data, diff --git a/src/Lobby.cc b/src/Lobby.cc index 71455b97..3b229795 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -246,7 +246,7 @@ uint8_t Lobby::effective_section_id() const { } auto leader = this->clients.at(this->leader_id); if (leader) { - return leader->character()->disp.visual.section_id; + return leader->character_file()->disp.visual.section_id; } return 0; } @@ -465,7 +465,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { // If the lobby is recording a battle record, add the player join event if (this->battle_record) { - auto p = c->character(); + auto p = c->character_file(); PlayerLobbyDataDCGC lobby_data; lobby_data.player_tag = 0x00010000; lobby_data.guild_card_number = c->login->account->account_id; @@ -607,7 +607,7 @@ shared_ptr Lobby::find_client(const string* identifier, uint64_t account if (account_id && lc->login && (lc->login->account->account_id == account_id)) { return lc; } - if (identifier && (lc->character()->disp.name.eq(*identifier, lc->language()))) { + if (identifier && (lc->character_file()->disp.name.eq(*identifier, lc->language()))) { return lc; } } @@ -644,7 +644,7 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr c, const s if (password && !this->password.empty() && (*password != this->password)) { return JoinError::INCORRECT_PASSWORD; } - auto p = c->character(); + auto p = c->character_file(); if (p->disp.stats.level < this->min_level) { return JoinError::LEVEL_TOO_LOW; } @@ -738,7 +738,7 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) { } void Lobby::assign_inventory_and_bank_item_ids(shared_ptr c, bool consume_ids) { - auto p = c->character(); + auto p = c->character_file(); uint32_t orig_next_item_id = this->next_item_id_for_client.at(c->lobby_client_id); for (size_t z = 0; z < p->inventory.num_items; z++) { p->inventory.items[z].data.id = this->generate_item_id(c->lobby_client_id); @@ -749,9 +749,9 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr c, bool consum if (c->log.info_f("Assigned inventory item IDs{}", consume_ids ? "" : " but did not mark IDs as used")) { c->print_inventory(); - auto& bank = c->current_bank(); - if (p->bank.num_items) { - bank.assign_ids(0x99000000 + (c->lobby_client_id << 20)); + auto bank = c->bank_file(); + if (!bank->items.empty()) { + bank->assign_ids(0x99000000 + (c->lobby_client_id << 20)); c->log.info_f("Assigned bank item IDs"); c->print_bank(); } else { diff --git a/src/PlayerFilesManager.cc b/src/PlayerFilesManager.cc deleted file mode 100644 index c499deb4..00000000 --- a/src/PlayerFilesManager.cc +++ /dev/null @@ -1,122 +0,0 @@ -#include "PlayerFilesManager.hh" - -#include -#include -#include - -#include -#include -#include - -#include "FileContentsCache.hh" -#include "ItemData.hh" -#include "Loggers.hh" -#include "PSOEncryption.hh" -#include "PSOProtocol.hh" -#include "StaticGameData.hh" -#include "Text.hh" -#include "Version.hh" - -using namespace std; - -PlayerFilesManager::PlayerFilesManager(std::shared_ptr io_context) - : io_context(io_context), - clear_expired_files_timer(*this->io_context) { - this->schedule_callback(); -} - -std::shared_ptr PlayerFilesManager::get_system(const std::string& filename) { - try { - return this->loaded_system_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -std::shared_ptr PlayerFilesManager::get_character(const std::string& filename) { - try { - return this->loaded_character_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -std::shared_ptr PlayerFilesManager::get_guild_card(const std::string& filename) { - try { - return this->loaded_guild_card_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -std::shared_ptr PlayerFilesManager::get_bank(const std::string& filename) { - try { - return this->loaded_bank_files.at(filename); - } catch (const out_of_range&) { - return nullptr; - } -} - -void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_system_files.emplace(filename, file).second) { - throw runtime_error("Guild Card file already loaded: " + filename); - } -} - -void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_character_files.emplace(filename, file).second) { - throw runtime_error("character file already loaded: " + filename); - } -} - -void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_guild_card_files.emplace(filename, file).second) { - throw runtime_error("Guild Card file already loaded: " + filename); - } -} - -void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr file) { - if (!this->loaded_bank_files.emplace(filename, file).second) { - throw runtime_error("bank file already loaded: " + filename); - } -} - -void PlayerFilesManager::schedule_callback() { - this->clear_expired_files_timer.expires_after(std::chrono::seconds(30)); - this->clear_expired_files_timer.async_wait(bind(&PlayerFilesManager::clear_expired_files, this)); -} - -template -size_t erase_unused(std::unordered_map>& m) { - size_t ret = 0; - for (auto it = m.begin(); it != m.end();) { - if (it->second.use_count() <= 1) { - it = m.erase(it); - ret++; - } else { - it++; - } - } - return ret; -} - -void PlayerFilesManager::clear_expired_files() { - size_t num_deleted = erase_unused(this->loaded_system_files); - if (num_deleted) { - player_data_log.info_f("Cleared {} expired system file(s)", num_deleted); - } - num_deleted = erase_unused(this->loaded_character_files); - if (num_deleted) { - player_data_log.info_f("Cleared {} expired character file(s)", num_deleted); - } - num_deleted = erase_unused(this->loaded_guild_card_files); - if (num_deleted) { - player_data_log.info_f("Cleared {} expired Guild Card file(s)", num_deleted); - } - num_deleted = erase_unused(this->loaded_bank_files); - if (num_deleted) { - player_data_log.info_f("Cleared {} expired bank file(s)", num_deleted); - } - - this->schedule_callback(); -} diff --git a/src/PlayerFilesManager.hh b/src/PlayerFilesManager.hh deleted file mode 100644 index 1c858527..00000000 --- a/src/PlayerFilesManager.hh +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "Episode3/DataIndexes.hh" -#include "ItemCreator.hh" -#include "ItemNameIndex.hh" -#include "LevelTable.hh" -#include "PlayerSubordinates.hh" -#include "SaveFileFormats.hh" -#include "Text.hh" -#include "Version.hh" - -class PlayerFilesManager { -public: - explicit PlayerFilesManager(std::shared_ptr io_context); - ~PlayerFilesManager() = default; - - std::shared_ptr get_system(const std::string& filename); - std::shared_ptr get_character(const std::string& filename); - std::shared_ptr get_guild_card(const std::string& filename); - std::shared_ptr get_bank(const std::string& filename); - - void set_system(const std::string& filename, std::shared_ptr file); - void set_character(const std::string& filename, std::shared_ptr file); - void set_guild_card(const std::string& filename, std::shared_ptr file); - void set_bank(const std::string& filename, std::shared_ptr file); - -private: - std::shared_ptr io_context; - asio::steady_timer clear_expired_files_timer; - - std::unordered_map> loaded_system_files; - std::unordered_map> loaded_character_files; - std::unordered_map> loaded_guild_card_files; - std::unordered_map> loaded_bank_files; - - void schedule_callback(); - void clear_expired_files(); -}; diff --git a/src/PlayerInventory.cc b/src/PlayerInventory.cc new file mode 100644 index 00000000..2581a4e4 --- /dev/null +++ b/src/PlayerInventory.cc @@ -0,0 +1,116 @@ +#include "PlayerInventory.hh" + +void PlayerBank::load(FILE* f) { + le_uint32_t num_items; + le_uint32_t meseta; + phosg::freadx(f, &num_items, sizeof(num_items)); + phosg::freadx(f, &meseta, sizeof(meseta)); + this->meseta = meseta; + this->items.reserve(num_items); + while (this->items.size() < num_items) { + auto& item = this->items.emplace_back(); + phosg::freadx(f, &item, sizeof(item)); + } +} + +void PlayerBank::save(FILE* f) const { + le_uint32_t num_items = this->items.size(); + le_uint32_t meseta = this->meseta; + phosg::fwritex(f, &num_items, sizeof(num_items)); + phosg::fwritex(f, &meseta, sizeof(meseta)); + for (const auto& item : this->items) { + phosg::fwritex(f, &item, sizeof(item)); + } +} + +uint32_t PlayerBank::bb_checksum() const { + le_uint32_t num_items = this->items.size(); + le_uint32_t meseta = this->meseta; + uint32_t ret = phosg::crc32(&num_items, sizeof(num_items)); + ret = phosg::crc32(&meseta, sizeof(meseta), ret); + for (const auto& item : this->items) { + ret = phosg::crc32(&item, sizeof(item), ret); + } + return ret; +} + +void PlayerBank::add_item(const ItemData& item, const ItemData::StackLimits& limits) { + uint32_t primary_identifier = item.primary_identifier(); + + if (primary_identifier == 0x04000000) { + this->meseta += item.data2d; + if (this->meseta > this->max_meseta) { + this->meseta = this->max_meseta; + } + return; + } + + size_t combine_max = item.max_stack_size(limits); + if (combine_max > 1) { + size_t y; + for (y = 0; y < this->items.size(); y++) { + if (this->items[y].data.primary_identifier() == primary_identifier) { + break; + } + } + + if (y < this->items.size()) { + uint8_t new_count = this->items[y].data.data1[5] + item.data1[5]; + if (new_count > combine_max) { + throw std::runtime_error("stack size would exceed limit"); + } + this->items[y].data.data1[5] = new_count; + this->items[y].amount = new_count; + return; + } + } + + if (this->items.size() >= this->max_items) { + throw std::runtime_error("no free space in bank"); + } + + auto& new_item = this->items.emplace_back(); + new_item.data = item; + new_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1; + new_item.present = 1; +} + +ItemData PlayerBank::remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) { + size_t index = this->find_item(item_id); + auto& bank_item = this->items[index]; + + ItemData ret = bank_item.data; + if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) { + ret.data1[5] = amount; + bank_item.data.data1[5] -= amount; + bank_item.amount -= amount; + } else { + this->items.erase(this->items.begin() + index); + } + return ret; +} + +size_t PlayerBank::find_item(uint32_t item_id) { + for (size_t x = 0; x < this->items.size(); x++) { + if (this->items[x].data.id == item_id) { + return x; + } + } + throw std::out_of_range("item not present"); +} + +void PlayerBank::sort() { + std::sort(this->items.begin(), this->items.end()); +} + +void PlayerBank::assign_ids(uint32_t base_id) { + for (size_t z = 0; z < this->items.size(); z++) { + this->items[z].data.id = base_id + z; + } +} + +void PlayerBank::enforce_stack_limits(std::shared_ptr stack_limits) { + for (auto& item : this->items) { + item.data.enforce_stack_size_limits(*stack_limits); + } +} diff --git a/src/PlayerInventory.hh b/src/PlayerInventory.hh index e4197a8a..06372670 100644 --- a/src/PlayerInventory.hh +++ b/src/PlayerInventory.hh @@ -315,95 +315,6 @@ struct PlayerBankT { /* 0008 */ parray, SlotCount> items; /* 05A8 for 60 items (v1/v2), 12C8 for 200 items (v3/v4) */ - uint32_t checksum() const { - return phosg::crc32(this, 2 * sizeof(U32T) + sizeof(PlayerBankItemT) * std::min(SlotCount, this->num_items)); - } - - void add_item(const ItemData& item, const ItemData::StackLimits& limits) { - uint32_t primary_identifier = item.primary_identifier(); - - if (primary_identifier == 0x04000000) { - this->meseta += item.data2d; - if (this->meseta > 999999) { - this->meseta = 999999; - } - return; - } - - size_t combine_max = item.max_stack_size(limits); - if (combine_max > 1) { - size_t y; - for (y = 0; y < this->num_items; y++) { - if (this->items[y].data.primary_identifier() == primary_identifier) { - break; - } - } - - if (y < this->num_items) { - uint8_t new_count = this->items[y].data.data1[5] + item.data1[5]; - if (new_count > combine_max) { - throw std::runtime_error("stack size would exceed limit"); - } - this->items[y].data.data1[5] = new_count; - this->items[y].amount = new_count; - return; - } - } - - if (this->num_items >= SlotCount) { - throw std::runtime_error("no free space in bank"); - } - auto& last_item = this->items[this->num_items]; - last_item.data = item; - last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1; - last_item.present = 1; - this->num_items++; - } - - ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) { - size_t index = this->find_item(item_id); - auto& bank_item = this->items[index]; - - ItemData ret; - if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) { - ret = bank_item.data; - ret.data1[5] = amount; - bank_item.data.data1[5] -= amount; - bank_item.amount -= amount; - return ret; - } - - ret = bank_item.data; - this->num_items--; - for (size_t x = index; x < this->num_items; x++) { - this->items[x] = this->items[x + 1]; - } - auto& last_item = this->items[this->num_items]; - last_item.amount = 0; - last_item.present = 0; - last_item.data.clear(); - return ret; - } - - size_t find_item(uint32_t item_id) { - for (size_t x = 0; x < this->num_items; x++) { - if (this->items[x].data.id == item_id) { - return x; - } - } - throw std::out_of_range("item not present"); - } - - void sort() { - std::sort(this->items.data(), this->items.data() + this->num_items); - } - - void assign_ids(uint32_t base_id) { - for (size_t z = 0; z < this->num_items; z++) { - this->items[z].data.id = base_id + z; - } - } - void decode_from_client(Version v) { for (size_t z = 0; z < this->items.size(); z++) { this->items[z].data.decode_for_version(v); @@ -416,12 +327,6 @@ struct PlayerBankT { } } - void enforce_stack_limits(std::shared_ptr stack_limits) { - for (size_t z = 0; z < std::min(this->num_items, this->items.size()); z++) { - this->items[z].data.enforce_stack_size_limits(*stack_limits); - } - } - template operator PlayerBankT() const { PlayerBankT ret; @@ -439,3 +344,46 @@ using PlayerBank200BE = PlayerBankT<200, true>; check_struct_size(PlayerBank60, 0x05A8); check_struct_size(PlayerBank200, 0x12C8); check_struct_size(PlayerBank200BE, 0x12C8); + +struct PlayerBank { + uint32_t max_meseta = 999999; + uint32_t max_items = 200; + uint32_t meseta = 0; + std::vector items; + + PlayerBank() = default; + + template + PlayerBank(const PlayerBankT& src) + : max_meseta(999999), max_items(SrcSlotCount), meseta(src.meseta) { + this->items.reserve(src.num_items); + for (size_t z = 0; z < src.num_items; z++) { + this->items.emplace_back(src.items[z]); + } + } + + template + operator PlayerBankT() const { + PlayerBankT ret; + ret.num_items = std::min(ret.items.size(), this->items.size()); + ret.meseta = this->meseta; + for (size_t z = 0; z < ret.num_items; z++) { + ret.items[z] = this->items[z]; + } + return ret; + } + + void load(FILE* f); + void save(FILE* f) const; + + uint32_t bb_checksum() const; + + void add_item(const ItemData& item, const ItemData::StackLimits& limits); + ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits); + size_t find_item(uint32_t item_id); + void sort(); + + void assign_ids(uint32_t base_id); + + void enforce_stack_limits(std::shared_ptr stack_limits); +}; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index ea78c951..ee812f81 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1600,7 +1600,7 @@ static asio::awaitable S_64(shared_ptr c, Channel::Messag } else { c->proxy_session->lobby_event = 0; c->proxy_session->lobby_difficulty = 0; - c->proxy_session->lobby_section_id = c->character()->disp.visual.section_id; + c->proxy_session->lobby_section_id = c->character_file()->disp.visual.section_id; c->proxy_session->lobby_mode = GameMode::NORMAL; c->proxy_session->lobby_random_seed = phosg::random_object(); } diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index ae309f25..a360693d 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1397,6 +1397,7 @@ static asio::awaitable on_93_BB(shared_ptr c, Channel::Message& ms c->sub_version = base_cmd.sub_version; // c->channel->language set after version check c->bb_character_index = base_cmd.character_slot; + c->bb_bank_character_index = base_cmd.character_slot; c->bb_connection_phase = base_cmd.connection_phase; c->bb_client_code = base_cmd.client_code; c->bb_security_token = base_cmd.security_token; @@ -1968,7 +1969,7 @@ static asio::awaitable on_CA_Ep3(shared_ptr c, Channel::Message& m l->battle_record = make_shared(s->ep3_behavior_flags); for (auto existing_c : l->clients) { if (existing_c) { - auto existing_p = existing_c->character(); + auto existing_p = existing_c->character_file(); PlayerLobbyDataDCGC lobby_data; lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language()); lobby_data.player_tag = 0x00010000; @@ -2209,7 +2210,7 @@ static asio::awaitable on_09(shared_ptr c, Channel::Message& msg) const char* version_token = (game_c->version() != c->version()) ? version_tokens.at(static_cast(game_c->version())) : ""; - auto player = game_c->character(); + auto player = game_c->character_file(); string name = escape_player_name(player->disp.name.decode(game_c->language())); info += std::format("{}{}\n {} Lv{} {}\n", name, @@ -2388,14 +2389,14 @@ static void on_quest_loaded(shared_ptr l) { lc->delete_overlay(); if (l->quest->battle_rules) { - lc->use_default_bank(); + lc->change_bank(lc->bb_character_index); lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version())); lc->log.info_f("Created battle overlay"); } else if (l->quest->challenge_template_index >= 0 && !is_v4(lc->version())) { // On BB, the client will send a sequence of DF commands that creates the // overlay; on non-BB, we do it at quest start time instead (hence the // version check above). - lc->use_default_bank(); + lc->change_bank(lc->bb_character_index); lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version())); lc->log.info_f("Created challenge overlay"); l->assign_inventory_and_bank_item_ids(lc, true); @@ -2924,7 +2925,7 @@ static asio::awaitable on_10_tournament_entries( co_return; } if (team_name.empty()) { - team_name = c->character()->disp.name.decode(c->language()); + team_name = c->character_file()->disp.name.decode(c->language()); team_name += std::format("/{:X}", c->login->account->account_id); } uint16_t tourn_num = item_id >> 16; @@ -3337,7 +3338,7 @@ static asio::awaitable on_61_98(shared_ptr c, Channel::Message& ms c->clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_PLAYER_STATES); } - auto player = c->character(); + auto player = c->character_file(); switch (c->version()) { case Version::DC_NTE: @@ -3642,7 +3643,7 @@ static asio::awaitable on_06(shared_ptr c, Channel::Message& msg) co_return; } - auto p = c->character(); + auto p = c->character_file(); string from_name = p->disp.name.decode(c->language()); static const string whisper_text = "(whisper)"; for (size_t x = 0; x < l->max_clients; x++) { @@ -3696,6 +3697,7 @@ static asio::awaitable on_E3_BB(shared_ptr c, Channel::Message& ms if (c->bb_connection_phase != 0x00) { c->save_and_unload_character(); c->bb_character_index = cmd.character_index; + c->bb_bank_character_index = cmd.character_index; send_approve_player_choice_bb(c); } else { @@ -3707,8 +3709,9 @@ static asio::awaitable on_E3_BB(shared_ptr c, Channel::Message& ms auto send_preview = [&c](size_t index) -> void { c->save_and_unload_character(); c->bb_character_index = index; + c->bb_bank_character_index = index; try { - auto preview = c->character()->to_preview(); + auto preview = c->character_file()->to_preview(); send_player_preview_bb(c, c->bb_character_index, &preview); } catch (const exception& e) { @@ -3793,7 +3796,7 @@ static asio::awaitable on_E8_BB(shared_ptr c, Channel::Message& ms } } if (c->login && new_gc.guild_card_number == c->login->account->account_id) { - c->character(true, false)->guild_card.description = new_gc.description; + c->character_file(true, false)->guild_card.description = new_gc.description; c->log.info_f("Updated character's guild card"); } break; @@ -3919,18 +3922,20 @@ static asio::awaitable on_E5_BB(shared_ptr c, Channel::Message& ms co_return; } - if (c->character(false).get()) { + if (c->character_file(false).get()) { throw runtime_error("player already exists"); } c->bb_character_index = -1; + c->bb_bank_character_index = -1; c->system_file(); // Ensure system file is loaded c->bb_character_index = cmd.character_index; + c->bb_bank_character_index = cmd.character_index; bool should_send_approve = true; if (c->bb_connection_phase == 0x03) { // Dressing room try { - c->character()->disp.apply_dressing_room(cmd.preview); + c->character_file()->disp.apply_dressing_room(cmd.preview); } catch (const exception& e) { send_message_box(c, std::format("$C6Character could not be modified:\n{}", e.what())); should_send_approve = false; @@ -3955,17 +3960,17 @@ static asio::awaitable on_ED_BB(shared_ptr c, Channel::Message& ms switch (msg.command) { case 0x01ED: { const auto& cmd = check_size_t(msg.data); - c->character(true, false)->option_flags = cmd.option_flags; + c->character_file(true, false)->option_flags = cmd.option_flags; break; } case 0x02ED: { const auto& cmd = check_size_t(msg.data); - c->character(true, false)->symbol_chats = cmd.symbol_chats; + c->character_file(true, false)->symbol_chats = cmd.symbol_chats; break; } case 0x03ED: { const auto& cmd = check_size_t(msg.data); - c->character(true, false)->shortcuts = cmd.chat_shortcuts; + c->character_file(true, false)->shortcuts = cmd.chat_shortcuts; break; } case 0x04ED: { @@ -3982,17 +3987,17 @@ static asio::awaitable on_ED_BB(shared_ptr c, Channel::Message& ms } case 0x06ED: { const auto& cmd = check_size_t(msg.data); - c->character(true, false)->tech_menu_shortcut_entries = cmd.tech_menu; + c->character_file(true, false)->tech_menu_shortcut_entries = cmd.tech_menu; break; } case 0x07ED: { const auto& cmd = check_size_t(msg.data); - c->character()->disp.config = cmd.customize; + c->character_file()->disp.config = cmd.customize; break; } case 0x08ED: { const auto& cmd = check_size_t(msg.data); - c->character(true, false)->challenge_records = cmd.records; + c->character_file(true, false)->challenge_records = cmd.records; break; } default: @@ -4007,7 +4012,7 @@ static asio::awaitable on_E7_BB(shared_ptr c, Channel::Message& ms // TODO: In the future, we shouldn't need to trust any of the client's data // here. We should instead verify our copy of the player against what the // client sent, and alert on anything that's out of sync. - auto p = c->character(); + auto p = c->character_file(); p->challenge_records = cmd.char_file.challenge_records; p->battle_records = cmd.char_file.battle_records; p->death_count = cmd.char_file.death_count; @@ -4062,7 +4067,7 @@ static asio::awaitable on_DF_BB(shared_ptr c, Channel::Message& ms // On non-BB, there is no DF command, and overlays are created at quest // start time instead, hence the version check here. if (lc && is_v4(lc->version())) { - lc->use_default_bank(); + lc->change_bank(lc->bb_character_index); lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version())); lc->log.info_f("Created challenge overlay"); l->assign_inventory_and_bank_item_ids(lc, true); @@ -4111,7 +4116,7 @@ static asio::awaitable on_DF_BB(shared_ptr c, Channel::Message& ms case 0x07DF: { const auto& cmd = check_size_t(msg.data); - auto p = c->character(true, false); + auto p = c->character_file(true, false); auto& award_state = (l->episode == Episode::EP2) ? p->challenge_records.ep2_online_award_state : p->challenge_records.ep1_online_award_state; @@ -4159,7 +4164,7 @@ static asio::awaitable on_C0(shared_ptr c, Channel::Message&) { } static asio::awaitable on_C2(shared_ptr c, Channel::Message& msg) { - c->character()->choice_search_config = check_size_t(msg.data); + c->character_file()->choice_search_config = check_size_t(msg.data); co_return; } @@ -4170,7 +4175,7 @@ static void on_choice_search_t(shared_ptr c, const ChoiceSearchConfig& c vector results; for (const auto& l : s->all_lobbies()) { for (const auto& lc : l->clients) { - if (!lc || !lc->login || lc->character()->choice_search_config.disabled) { + if (!lc || !lc->login || lc->character_file()->choice_search_config.disabled) { continue; } @@ -4191,7 +4196,7 @@ static void on_choice_search_t(shared_ptr c, const ChoiceSearchConfig& c } if (is_match) { - auto lp = lc->character(); + auto lp = lc->character_file(); auto& result = results.emplace_back(); result.guild_card_number = lc->login->account->account_id; result.name.encode(lp->disp.name.decode(lc->language()), c->language()); @@ -4336,7 +4341,7 @@ static asio::awaitable on_81(shared_ptr c, Channel::Message& msg) // If the target has auto-reply enabled, send the autoreply. Note that we also // forward the message in this case. if (!c->blocked_senders.count(target->login->account->account_id)) { - auto target_p = target->character(); + auto target_p = target->character_file(); if (!target_p->auto_reply.empty()) { send_simple_mail( c, @@ -4350,7 +4355,7 @@ static asio::awaitable on_81(shared_ptr c, Channel::Message& msg) send_simple_mail( target, c->login->account->account_id, - c->character()->disp.name.decode(c->language()), + c->character_file()->disp.name.decode(c->language()), message); } } @@ -4368,7 +4373,7 @@ static asio::awaitable on_D9(shared_ptr c, Channel::Message& msg) msg.data.push_back(0); } try { - c->character(true, false)->info_board.encode(tt_decode_marked(msg.data, c->language(), is_w), c->language()); + c->character_file(true, false)->info_board.encode(tt_decode_marked(msg.data, c->language(), is_w), c->language()); } catch (const runtime_error& e) { c->log.warning_f("Failed to decode info board message: {}", e.what()); } @@ -4383,7 +4388,7 @@ static asio::awaitable on_C7(shared_ptr c, Channel::Message& msg) } string message = tt_decode_marked(msg.data, c->language(), is_w); - c->character(true, false)->auto_reply.encode(message, c->language()); + c->character_file(true, false)->auto_reply.encode(message, c->language()); c->login->account->auto_reply_message = message; c->login->account->save(); co_return; @@ -4391,7 +4396,7 @@ static asio::awaitable on_C7(shared_ptr c, Channel::Message& msg) static asio::awaitable on_C8(shared_ptr c, Channel::Message& msg) { check_size_v(msg.data.size(), 0); - c->character(true, false)->auto_reply.clear(); + c->character_file(true, false)->auto_reply.clear(); c->login->account->auto_reply_message.clear(); c->login->account->save(); co_return; @@ -4442,7 +4447,7 @@ shared_ptr create_game_generic( size_t min_level = s->default_min_level_for_game(creator_c->version(), episode, difficulty); - auto p = creator_c->character(); + auto p = creator_c->character_file(); if (!creator_c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) { // Note: We don't throw here because this is a situation players might // actually encounter while playing the game normally @@ -5121,8 +5126,8 @@ static asio::awaitable on_D2_V3_BB(shared_ptr c, Channel::Message& if (to_c->version() == Version::BB_V4) { // On BB, the server is expected to generate the delete item and create // item commands - auto to_p = to_c->character(); - auto from_p = from_c->character(); + auto to_p = to_c->character_file(); + auto from_p = from_c->character_file(); for (const auto& trade_item : from_c->pending_item_trade->items) { size_t amount = trade_item.stack_size(*s->item_stack_limits(from_c->version())); @@ -5316,7 +5321,7 @@ static asio::awaitable on_EA_BB(shared_ptr c, Channel::Message& ms // TODO: What's the right error code to use here? send_command(c, 0x02EA, 0x00000001); } else { - string player_name = c->character()->disp.name.decode(c->language()); + string player_name = c->character_file()->disp.name.decode(c->language()); auto team = s->team_index->create(team_name, c->login->account->account_id, player_name); c->login->account->bb_team_id = team->team_id; c->login->account->save(); @@ -5355,7 +5360,7 @@ static asio::awaitable on_EA_BB(shared_ptr c, Channel::Message& ms s->team_index->add_member( team->team_id, added_c->login->account->account_id, - added_c->character()->disp.name.decode(added_c->language())); + added_c->character_file()->disp.name.decode(added_c->language())); send_command(c, 0x04EA, 0x00000000); send_command(added_c, 0x04EA, 0x00000000); send_team_metadata_change_notifications( @@ -5516,7 +5521,7 @@ static asio::awaitable on_EA_BB(shared_ptr c, Channel::Message& ms send_team_metadata_change_notifications(s, team, 0, TeamMetadataChange::REWARD_FLAGS); } if (!reward.reward_item.empty()) { - c->current_bank().add_item(reward.reward_item, *s->item_stack_limits(c->version())); + c->bank_file()->add_item(reward.reward_item, *s->item_stack_limits(c->version())); } } break; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 96b74766..3dd9f3bd 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -624,7 +624,7 @@ static asio::awaitable on_sync_joining_player_compressed_state(shared_ptr< } auto l = c->require_lobby(); - size_t target_num_items = target->character()->inventory.num_items; + size_t target_num_items = target->character_file()->inventory.num_items; for (size_t z = 0; z < 12; z++) { uint32_t client_next_id = decompressed_cmd->next_item_id_per_player[z]; uint32_t server_next_id = l->next_item_id_for_client[z]; @@ -1422,20 +1422,20 @@ static asio::awaitable on_send_guild_card(shared_ptr c, Subcommand switch (c->version()) { case Version::DC_NTE: { const auto& cmd = msg.check_size_t(); - c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::DC_11_2000: case Version::DC_V1: case Version::DC_V2: { const auto& cmd = msg.check_size_t(); - c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::PC_NTE: case Version::PC_V2: { const auto& cmd = msg.check_size_t(); - c->character(true, false)->guild_card.description = cmd.guild_card.description; + c->character_file(true, false)->guild_card.description = cmd.guild_card.description; break; } case Version::GC_NTE: @@ -1443,12 +1443,12 @@ static asio::awaitable on_send_guild_card(shared_ptr c, Subcommand case Version::GC_EP3_NTE: case Version::GC_EP3: { const auto& cmd = msg.check_size_t(); - c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::XB_V3: { const auto& cmd = msg.check_size_t(); - c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); + c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language()); break; } case Version::BB_V4: @@ -1535,7 +1535,7 @@ static asio::awaitable on_word_select_t(shared_ptr c, SubcommandMe } } catch (const exception& e) { - string name = escape_player_name(c->character()->disp.name.decode(c->language())); + string name = escape_player_name(c->character_file()->disp.name.decode(c->language())); lc->log.warning_f("Untranslatable Word Select message: {}", e.what()); send_text_message_fmt(lc, "$C4Untranslatable Word\nSelect message from\n{}", name); } @@ -1641,7 +1641,7 @@ static asio::awaitable on_player_died(shared_ptr c, SubcommandMess // Decrease MAG's synchro try { - auto& inventory = c->character()->inventory; + auto& inventory = c->character_file()->inventory; size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG); auto& data = inventory.items[mag_index].data; data.data2[0] = max(static_cast(data.data2[0] - 5), 0); @@ -1946,7 +1946,7 @@ static asio::awaitable on_player_drop_item(shared_ptr c, Subcomman auto s = c->require_server_state(); auto l = c->require_lobby(); - auto p = c->character(); + auto p = c->character_file(); auto item = p->remove_item(cmd.item_id, 0, *s->item_stack_limits(c->version())); l->add_item(cmd.floor, item, cmd.pos, nullptr, nullptr, 0x00F); @@ -1995,7 +1995,7 @@ static asio::awaitable on_create_inventory_item_t(shared_ptr c, Su } } else { - c->character()->add_item(item, *s->item_stack_limits(c->version())); + c->character_file()->add_item(item, *s->item_stack_limits(c->version())); if (l->log.should_log(phosg::LogLevel::L_INFO)) { auto name = s->describe_item(c->version(), item); @@ -2075,7 +2075,7 @@ static asio::awaitable on_drop_partial_stack_bb(shared_ptr c, Subc } auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); const auto& limits = *s->item_stack_limits(c->version()); auto item = p->remove_item(cmd.item_id, cmd.amount, limits); @@ -2118,7 +2118,7 @@ static asio::awaitable on_buy_shop_item(shared_ptr c, SubcommandMe } auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); ItemData item = cmd.item_data; item.data2d = 0; // Clear the price field item.decode_for_version(c->version()); @@ -2261,7 +2261,7 @@ static asio::awaitable on_pick_up_item_generic( // it receives a 6x5A and the floor item exists, so we just implement that // logic here instead of forwarding the 6x5A to the leader. - auto p = c->character(); + auto p = c->character_file(); auto s = c->require_server_state(); auto fi = l->remove_item(floor, item_id, c->lobby_client_id); if (!fi->visible_to_client(c->lobby_client_id)) { @@ -2372,7 +2372,7 @@ static asio::awaitable on_equip_item(shared_ptr c, SubcommandMessa auto l = c->require_lobby(); EquipSlot slot = static_cast(cmd.equip_slot.load()); - auto p = c->character(); + auto p = c->character_file(); p->inventory.equip_item_id(cmd.item_id, slot, is_pre_v1(c->version())); c->log.info_f("Equipped item {:08X}", cmd.item_id); @@ -2387,7 +2387,7 @@ static asio::awaitable on_unequip_item(shared_ptr c, SubcommandMes } auto l = c->require_lobby(); - auto p = c->character(); + auto p = c->character_file(); p->inventory.unequip_item_id(cmd.item_id); c->log.info_f("Unequipped item {:08X}", cmd.item_id); @@ -2403,7 +2403,7 @@ static asio::awaitable on_use_item(shared_ptr c, SubcommandMessage auto l = c->require_lobby(); auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); size_t index = p->inventory.find_item(cmd.item_id); string name; { @@ -2432,7 +2432,7 @@ static asio::awaitable on_feed_mag(shared_ptr c, SubcommandMessage auto s = c->require_server_state(); auto l = c->require_lobby(); - auto p = c->character(); + auto p = c->character_file(); size_t mag_index = p->inventory.find_item(cmd.mag_item_id); size_t fed_index = p->inventory.find_item(cmd.fed_item_id); @@ -2534,7 +2534,7 @@ static asio::awaitable on_open_shop_bb_or_ep3_battle_subs(shared_ptr(); auto s = c->require_server_state(); - size_t level = c->character()->disp.stats.level + 1; + size_t level = c->character_file()->disp.stats.level + 1; switch (cmd.shop_type) { case 0: c->bb_shop_contents[0] = l->item_creator->generate_tool_shop_contents(level); @@ -2619,7 +2619,7 @@ static asio::awaitable on_ep3_private_word_select_bb_bank_action( const auto& cmd = msg.check_size_t(); s->word_select_table->validate(cmd.message, c->version()); - string from_name = c->character()->disp.name.decode(c->language()); + string from_name = c->character_file()->disp.name.decode(c->language()); static const string whisper_text = "(whisper)"; auto send_to_client = [&](shared_ptr lc) -> void { if (cmd.private_flags & (1 << lc->lobby_client_id)) { @@ -2671,21 +2671,21 @@ static asio::awaitable on_ep3_private_word_select_bb_bank_action( co_return; } - auto p = c->character(); - auto& bank = c->current_bank(); + auto p = c->character_file(); + auto bank = c->bank_file(); if (cmd.action == 0) { // Deposit if (cmd.item_id == 0xFFFFFFFF) { // Deposit Meseta if (cmd.meseta_amount > p->disp.stats.meseta) { l->log.info_f("Player {} attempted to deposit {} Meseta in the bank, but has only {} Meseta on hand", c->lobby_client_id, cmd.meseta_amount, p->disp.stats.meseta); - } else if ((bank.meseta + cmd.meseta_amount) > 999999) { + } else if ((bank->meseta + cmd.meseta_amount) > bank->max_meseta) { l->log.info_f("Player {} attempted to deposit {} Meseta in the bank, but already has {} Meseta in the bank", c->lobby_client_id, cmd.meseta_amount, p->disp.stats.meseta); } else { - bank.meseta += cmd.meseta_amount; + bank->meseta += cmd.meseta_amount; p->disp.stats.meseta -= cmd.meseta_amount; l->log.info_f("Player {} deposited {} Meseta in the bank (bank now has {}; inventory now has {})", - c->lobby_client_id, cmd.meseta_amount, bank.meseta, p->disp.stats.meseta); + c->lobby_client_id, cmd.meseta_amount, bank->meseta, p->disp.stats.meseta); } } else { // Deposit item @@ -2698,7 +2698,7 @@ static asio::awaitable on_ep3_private_word_select_bb_bank_action( if (item.id == 0xFFFFFFFF) { item.id = cmd.item_id; } - bank.add_item(item, limits); + bank->add_item(item, limits); send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true); if (l->log.should_log(phosg::LogLevel::L_INFO)) { @@ -2711,22 +2711,22 @@ static asio::awaitable on_ep3_private_word_select_bb_bank_action( } else if (cmd.action == 1) { // Take if (cmd.item_index == 0xFFFF) { // Take Meseta - if (cmd.meseta_amount > bank.meseta) { + if (cmd.meseta_amount > bank->meseta) { l->log.info_f("Player {} attempted to withdraw {} Meseta from the bank, but has only {} Meseta in the bank", - c->lobby_client_id, cmd.meseta_amount, bank.meseta); + c->lobby_client_id, cmd.meseta_amount, bank->meseta); } else if ((p->disp.stats.meseta + cmd.meseta_amount) > 999999) { l->log.info_f("Player {} attempted to withdraw {} Meseta from the bank, but already has {} Meseta on hand", c->lobby_client_id, cmd.meseta_amount, p->disp.stats.meseta); } else { - bank.meseta -= cmd.meseta_amount; + bank->meseta -= cmd.meseta_amount; p->disp.stats.meseta += cmd.meseta_amount; l->log.info_f("Player {} withdrew {} Meseta from the bank (bank now has {}; inventory now has {})", - c->lobby_client_id, cmd.meseta_amount, bank.meseta, p->disp.stats.meseta); + c->lobby_client_id, cmd.meseta_amount, bank->meseta, p->disp.stats.meseta); } } else { // Take item const auto& limits = *s->item_stack_limits(c->version()); - auto item = bank.remove_item(cmd.item_id, cmd.item_amount, limits); + auto item = bank->remove_item(cmd.item_id, cmd.item_amount, limits); item.id = l->generate_item_id(c->lobby_client_id); p->add_item(item, limits); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); @@ -2751,7 +2751,7 @@ static void on_sort_inventory_bb_inner(shared_ptr c, const SubcommandMes } const auto& cmd = msg.check_size_t(); - auto p = c->character(); + auto p = c->character_file(); // Make sure the set of item IDs passed in by the client exactly matches the // set of item IDs present in the inventory @@ -3123,7 +3123,7 @@ static asio::awaitable on_set_quest_flag(shared_ptr c, SubcommandM if (c->version() == Version::BB_V4) { auto s = c->require_server_state(); // TODO: Should we allow overlays here? - auto p = c->character(true, false); + auto p = c->character_file(true, false); if (should_set) { c->log.info_f("Setting quest flag {}:{:04X}", name_for_difficulty(difficulty), flag_num); p->quest_flags.set(difficulty, flag_num); @@ -3721,7 +3721,7 @@ static asio::awaitable on_charge_attack_bb(shared_ptr c, Subcomman } const auto& cmd = msg.check_size_t(); - auto& disp = c->character()->disp; + auto& disp = c->character_file()->disp; if (cmd.meseta_amount > disp.stats.meseta) { disp.stats.meseta = 0; } else { @@ -3745,7 +3745,7 @@ static void send_max_level_notification_if_needed(shared_ptr c) { max_level = 998; } - auto p = c->character(); + auto p = c->character_file(); if (p->disp.stats.level == max_level) { string name = p->disp.name.decode(c->language()); size_t level_for_str = max_level + 1; @@ -3769,7 +3769,7 @@ static asio::awaitable on_level_up(shared_ptr c, SubcommandMessage // On the DC prototypes, this command doesn't include any stats - it just // increments the player's level by 1. - auto p = c->character(); + auto p = c->character_file(); if (is_pre_v1(c->version())) { msg.check_size_t(); auto s = c->require_server_state(); @@ -3800,7 +3800,7 @@ static asio::awaitable on_level_up(shared_ptr c, SubcommandMessage static void add_player_exp(shared_ptr c, uint32_t exp) { auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); p->disp.stats.experience += exp; if (c->version() == Version::BB_V4) { @@ -3886,8 +3886,8 @@ static asio::awaitable on_steal_exp_bb(shared_ptr c, SubcommandMes auto s = c->require_server_state(); const auto& cmd = msg.check_size_t(); - auto p = c->character(); - if (c->character()->disp.stats.level >= 199) { + auto p = c->character_file(); + if (c->character_file()->disp.stats.level >= 199) { co_return; } @@ -4005,7 +4005,7 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco ene_st->e_id, phosg::name_for_enum(type)); } - if (lc->character()->disp.stats.level < 199) { + if (lc->character_file()->disp.stats.level < 199) { add_player_exp(lc, player_exp); } } @@ -4014,7 +4014,7 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco // Update kill counts on unsealable items, but only for the player who // actually killed the enemy if (ene_st->last_hit_by_client_id(client_id)) { - auto& inventory = lc->character()->inventory; + auto& inventory = lc->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(lc->version())->is_unsealable_item(item.data)) { @@ -4028,7 +4028,7 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco static asio::awaitable on_adjust_player_meseta_bb(shared_ptr c, SubcommandMessage& msg) { const auto& cmd = msg.check_size_t(); - auto p = c->character(); + auto p = c->character_file(); if (cmd.amount < 0) { if (-cmd.amount > static_cast(p->disp.stats.meseta)) { p->disp.stats.meseta = 0; @@ -4072,7 +4072,7 @@ static asio::awaitable on_item_reward_request_bb(shared_ptr c, Sub // from the server. To handle this, we simply ignore any 6xCA command if the // item can't be created. try { - c->character()->add_item(item, limits); + 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); @@ -4109,7 +4109,7 @@ asio::awaitable on_transfer_item_via_mail_message_bb(shared_ptr c, } auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); const auto& limits = *s->item_stack_limits(c->version()); auto item = p->remove_item(cmd.item_id, cmd.amount, limits); @@ -4127,10 +4127,10 @@ asio::awaitable on_transfer_item_via_mail_message_bb(shared_ptr c, auto target_c = s->find_client(nullptr, cmd.target_guild_card_number); if (target_c && (target_c->version() == Version::BB_V4) && - (target_c->character(false) != nullptr) && + (target_c->character_file(false) != nullptr) && !target_c->check_flag(Client::Flag::AT_BANK_COUNTER)) { try { - target_c->current_bank().add_item(item, limits); + target_c->bank_file()->add_item(item, limits); item_sent = true; } catch (const runtime_error&) { } @@ -4174,7 +4174,7 @@ static asio::awaitable on_exchange_item_for_team_points_bb(shared_ptrrequire_server_state(); - auto p = c->character(); + auto p = c->character_file(); auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version())); size_t points = s->item_parameter_table(Version::BB_V4)->get_item_team_points(item); @@ -4210,7 +4210,7 @@ static asio::awaitable on_destroy_inventory_item(shared_ptr c, Sub } auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version())); if (l->log.should_log(phosg::LogLevel::L_INFO)) { @@ -4306,7 +4306,7 @@ static asio::awaitable on_identify_item_bb(shared_ptr c, Subcomman co_return; } - auto p = c->character(); + auto p = c->character_file(); size_t x = p->inventory.find_item(cmd.item_id); if (p->inventory.items[x].data.data1[0] != 0) { throw runtime_error("non-weapon items cannot be unidentified"); @@ -4347,7 +4347,7 @@ static asio::awaitable on_accept_identify_item_bb(shared_ptr c, Su throw runtime_error("accepted item ID does not match previous identify request"); } auto s = c->require_server_state(); - c->character()->add_item(c->bb_identify_result, *s->item_stack_limits(c->version())); + c->character_file()->add_item(c->bb_identify_result, *s->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(); } @@ -4366,7 +4366,7 @@ static asio::awaitable on_sell_item_at_shop_bb(shared_ptr c, Subco const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + 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; p->add_meseta(price); @@ -4405,7 +4405,7 @@ static asio::awaitable on_buy_shop_item_bb(shared_ptr c, Subcomman size_t price = item.data2d * cmd.amount; item.data2d = 0; - auto p = c->character(); + auto p = c->character_file(); p->remove_meseta(price, false); item.id = cmd.shop_item_id; @@ -4431,7 +4431,7 @@ static asio::awaitable on_medical_center_bb(shared_ptr c, Subcomma throw runtime_error("6xC5 command sent in non-game lobby"); } - c->character()->remove_meseta(10, false); + c->character_file()->remove_meseta(10, false); co_return; } @@ -4468,7 +4468,7 @@ static asio::awaitable on_battle_restart_bb(shared_ptr c, Subcomma for (auto& lc : l->clients) { if (lc) { lc->delete_overlay(); - lc->use_default_bank(); + lc->change_bank(lc->bb_character_index); lc->create_battle_overlay(new_rules, s->level_table(c->version())); } } @@ -4495,7 +4495,7 @@ static asio::awaitable on_battle_level_up_bb(shared_ptr c, Subcomm auto lc = l->clients.at(cmd.header.client_id); if (lc) { auto s = c->require_server_state(); - auto lp = lc->character(); + auto lp = lc->character_file(); uint32_t target_level = min(lp->disp.stats.level + cmd.num_levels, 199); uint32_t before_exp = lp->disp.stats.experience; int32_t exp_delta = lp->disp.stats.experience - before_exp; @@ -4554,7 +4554,7 @@ static asio::awaitable on_challenge_mode_retry_or_quit(shared_ptr for (auto lc : l->clients) { if (lc) { - lc->use_default_bank(); + lc->change_bank(lc->bb_character_index); lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(c->version())); lc->log.info_f("Created challenge overlay"); l->assign_inventory_and_bank_item_ids(lc, true); @@ -4580,7 +4580,7 @@ static asio::awaitable on_challenge_update_records(shared_ptr c, S co_return; } - auto p = c->character(true, false); + auto p = c->character_file(true, false); Version c_version = c->version(); switch (c_version) { case Version::DC_V2: @@ -4708,7 +4708,7 @@ static asio::awaitable on_quest_exchange_item_bb(shared_ptr c, Sub auto s = c->require_server_state(); try { - auto p = c->character(); + auto p = c->character_file(); const auto& limits = *s->item_stack_limits(c->version()); size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); @@ -4744,7 +4744,7 @@ static asio::awaitable on_wrap_item_bb(shared_ptr c, SubcommandMes const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); auto item = p->remove_item(cmd.item.id, 1, *s->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); @@ -4766,7 +4766,7 @@ static asio::awaitable on_photon_drop_exchange_for_item_bb(shared_ptrrequire_server_state(); try { - auto p = c->character(); + auto p = c->character_file(); const auto& limits = *s->item_stack_limits(c->version()); size_t found_index = p->inventory.find_item_by_primary_identifier(0x03100000); @@ -4804,7 +4804,7 @@ static asio::awaitable on_photon_drop_exchange_for_s_rank_special_bb(share const auto& limits = *s->item_stack_limits(c->version()); try { - auto p = c->character(); + auto p = c->character_file(); static const array costs({60, 60, 20, 20, 30, 30, 30, 50, 40, 50, 40, 40, 50, 40, 40, 40}); uint8_t cost = costs.at(cmd.special_type); @@ -4851,7 +4851,7 @@ static asio::awaitable on_secret_lottery_ticket_exchange_bb(shared_ptrcharacter(); + auto p = c->character_file(); ssize_t slt_index = -1; try { slt_index = p->inventory.find_item_by_primary_identifier(0x03100300); // Secret Lottery Ticket @@ -4912,7 +4912,7 @@ static asio::awaitable on_photon_crystal_exchange_bb(shared_ptr c, msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + 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())); send_destroy_item_to_lobby(c, item.id, 1); @@ -4976,7 +4976,7 @@ static asio::awaitable on_quest_F95F_result_bb(shared_ptr c, Subco const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); const auto& result = s->quest_F95F_results.at(cmd.result_index); if (result.second.empty()) { @@ -5023,7 +5023,7 @@ static asio::awaitable on_quest_F960_result_bb(shared_ptr c, Subco const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); time_t t_secs = phosg::now() / 1000000; struct tm t_parsed; @@ -5103,7 +5103,7 @@ static asio::awaitable on_momoka_item_exchange_bb(shared_ptr c, Su const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); try { const auto& limits = *s->item_stack_limits(c->version()); size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); @@ -5144,7 +5144,7 @@ static asio::awaitable on_upgrade_weapon_attribute_bb(shared_ptr c const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - auto p = c->character(); + auto p = c->character_file(); try { size_t item_index = p->inventory.find_item(cmd.item_id); auto& item = p->inventory.items[item_index].data; @@ -5195,7 +5195,7 @@ static asio::awaitable on_upgrade_weapon_attribute_bb(shared_ptr c static asio::awaitable on_write_quest_counter_bb(shared_ptr c, SubcommandMessage& msg) { const auto& cmd = msg.check_size_t(); - c->character()->quest_counters[cmd.index] = cmd.value; + c->character_file()->quest_counters[cmd.index] = cmd.value; co_return; } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 04443ab7..5c764705 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -790,7 +790,7 @@ void send_approve_player_choice_bb(shared_ptr c) { } void send_complete_player_bb(shared_ptr c) { - auto p = c->character(true, false); + auto p = c->character_file(true, false); auto sys = c->system_file(true); auto team = c->team(); if (c->check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) { @@ -1151,7 +1151,7 @@ void send_info_board_t(shared_ptr c) { if (!other_c.get()) { continue; } - auto other_p = other_c->character(true, false); + auto other_p = other_c->character_file(true, false); auto& e = entries.emplace_back(); e.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language()); e.message.encode(add_color(other_p->info_board.decode(other_p->inventory.language)), c->language()); @@ -1235,7 +1235,7 @@ void send_card_search_result_t( cmd.location_string.encode(location_string, c->language()); cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY; cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id; - auto rp = result->character(true, false); + auto rp = result->character_file(true, false); cmd.extension.player_name.encode(rp->disp.name.decode(rp->inventory.language), c->language()); send_command_t(c, 0x41, 0x00, cmd); @@ -1393,7 +1393,7 @@ void send_guild_card(shared_ptr c, shared_ptr source) { throw runtime_error("source player does not have an account"); } - auto source_p = source->character(true, false); + auto source_p = source->character_file(true, false); auto source_team = source->team(); uint64_t xb_user_id = (source->login->xb_license && source->login->xb_license->user_id) @@ -1783,7 +1783,7 @@ template void send_player_records_t(shared_ptr c, shared_ptr l, shared_ptr joining_client) { vector entries; auto add_client = [&](shared_ptr lc) -> void { - auto lp = lc->character(true, false); + auto lp = lc->character_file(true, false); auto& e = entries.emplace_back(); e.client_id = lc->lobby_client_id; e.challenge = lp->challenge_records; @@ -1808,7 +1808,7 @@ void populate_lobby_data_for_client(LobbyDataT& ret, shared_ptr c, ret.player_tag = 0x00010000; ret.guild_card_number = c->login->account->account_id; ret.client_id = c->lobby_client_id; - string name = c->character()->disp.name.decode(c->language()); + string name = c->character_file()->disp.name.decode(c->language()); ret.name.encode(name, viewer_c->language()); } @@ -1822,7 +1822,7 @@ void populate_lobby_data_for_client(PlayerLobbyDataXB& ret, shared_ptrlogin->account->account_id; } ret.client_id = c->lobby_client_id; - string name = c->character()->disp.name.decode(c->language()); + string name = c->character_file()->disp.name.decode(c->language()); ret.name.encode(name, viewer_c->language()); } @@ -1839,7 +1839,7 @@ void populate_lobby_data_for_client(PlayerLobbyDataBB& ret, s ret.team_master_guild_card_number = 0; ret.team_id = 0; } - string name = c->character()->disp.name.decode(c->language()); + string name = c->character_file()->disp.name.decode(c->language()); ret.name.encode(name, viewer_c->language()); } @@ -1875,7 +1875,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) if (!wc) { continue; } - auto wc_p = wc->character(); + auto wc_p = wc->character_file(); auto& p = cmd.players[z]; populate_lobby_data_for_client(p.lobby_data, wc, c); p.inventory = wc_p->inventory; @@ -1945,7 +1945,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) for (size_t z = 4; z < 12; z++) { if (l->clients[z]) { auto other_c = l->clients[z]; - auto other_p = other_c->character(); + auto other_p = other_c->character_file(); auto& cmd_p = cmd.spectator_players[z - 4]; auto& cmd_e = cmd.entries[z]; populate_lobby_data_for_client(cmd_p.lobby_data, other_c, c); @@ -2069,7 +2069,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { for (size_t x = 0; x < 4; x++) { auto lc = l->clients[x]; if (lc) { - auto other_p = lc->character(); + 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())); @@ -2181,7 +2181,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrcharacter(); + auto lp = lc->character_file(); auto& e = cmd.entries[used_entries++]; populate_lobby_data_for_client(e.lobby_data, lc, c); e.inventory = lp->inventory; @@ -2254,7 +2254,7 @@ void send_join_lobby_xb(shared_ptr c, shared_ptr l, shared_ptrcharacter(); + auto lp = lc->character_file(); auto& e = cmd.entries[used_entries++]; populate_lobby_data_for_client(e.lobby_data, lc, c); e.inventory = lp->inventory; @@ -2303,7 +2303,7 @@ void send_join_lobby_dc_nte(shared_ptr c, shared_ptr l, size_t used_entries = 0; for (const auto& lc : lobby_clients) { - auto lp = lc->character(); + auto lp = lc->character_file(); auto& e = cmd.entries[used_entries++]; populate_lobby_data_for_client(e.lobby_data, lc, c); e.inventory = lp->inventory; @@ -2958,7 +2958,7 @@ void send_game_flag_state_t(shared_ptr c) { cmd.header.subcommand = 0x6F; cmd.header.size = sizeof(CmdT) >> 2; cmd.header.unused = 0x0000; - cmd.quest_flags = (l && !l->quest_flags_known) ? *l->quest_flag_values : c->character()->quest_flags; + cmd.quest_flags = (l && !l->quest_flags_known) ? *l->quest_flag_values : c->character_file()->quest_flags; if (c->game_join_command_queue) { c->log.info_f("Client not ready to receive join commands; adding to queue"); @@ -3007,7 +3007,7 @@ void send_game_player_state(shared_ptr to_c, shared_ptr from_c, } if (apply_overrides) { - auto from_p = from_c->character(); + auto from_p = from_c->character_file(); to_send.base.pos.x = from_c->pos.x; to_send.base.pos.y = 0.0; to_send.base.pos.z = from_c->pos.z; @@ -3172,17 +3172,15 @@ void send_bank(shared_ptr c) { throw logic_error("6xBC can only be sent to BB clients"); } - auto p = c->character(); - auto& bank = c->current_bank(); - bank.sort(); - const auto* items_it = bank.items.data(); - vector items(items_it, items_it + bank.num_items); + auto p = c->character_file(); + auto bank = c->bank_file(); + bank->sort(); G_BankContentsHeader_BB_6xBC cmd = { - {{0xBC, 0, 0}, sizeof(G_BankContentsHeader_BB_6xBC) + items.size() * sizeof(PlayerBankItem)}, - bank.checksum(), bank.num_items, bank.meseta}; + {{0xBC, 0, 0}, sizeof(G_BankContentsHeader_BB_6xBC) + bank->items.size() * sizeof(PlayerBankItem)}, + bank->bb_checksum(), bank->items.size(), bank->meseta}; - send_command_t_vt(c, 0x6C, 0x00, cmd, items); + send_command_t_vt(c, 0x6C, 0x00, cmd, bank->items); } void send_shop(shared_ptr c, uint8_t shop_type) { @@ -3208,7 +3206,7 @@ void send_shop(shared_ptr c, uint8_t shop_type) { void send_level_up(shared_ptr c) { auto l = c->require_lobby(); - auto p = c->character(); + auto p = c->character_file(); CharacterStats stats = p->disp.stats.char_stats; const ItemData* mag = nullptr; @@ -3499,7 +3497,7 @@ string ep3_description_for_client(shared_ptr c) { if (!is_ep3(c->version())) { throw runtime_error("client is not Episode 3"); } - auto p = c->character(); + auto p = c->character_file(); return std::format( "{} CLv{} {}", name_for_char_class(p->disp.visual.char_class), @@ -3549,7 +3547,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { if (player.is_human()) { try { auto other_c = account_id_to_client.at(player.account_id); - entry.name.encode(other_c->character()->disp.name.decode(other_c->language()), c->language()); + entry.name.encode(other_c->character_file()->disp.name.decode(other_c->language()), c->language()); entry.description.encode(ep3_description_for_client(other_c), c->language()); } catch (const out_of_range&) { entry.name.encode(player.player_name, c->language()); @@ -3570,7 +3568,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { for (auto spec_c : l->clients) { if (spec_c) { auto& entry = cmd.spectator_entries[cmd.num_spectators++]; - entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language()); + entry.name.encode(spec_c->character_file()->disp.name.decode(spec_c->language()), c->language()); entry.description.encode(ep3_description_for_client(spec_c), c->language()); } } @@ -3587,7 +3585,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { size_t num_players = 0; for (const auto& opp_c : primary_lobby->clients) { if (opp_c) { - cmd.player_entries[num_players].name.encode(opp_c->character()->disp.name.decode(opp_c->language()), c->language()); + cmd.player_entries[num_players].name.encode(opp_c->character_file()->disp.name.decode(opp_c->language()), c->language()); cmd.player_entries[num_players].description.encode(ep3_description_for_client(opp_c), c->language()); num_players++; } @@ -3600,7 +3598,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { for (auto spec_c : l->clients) { if (spec_c) { auto& entry = cmd.spectator_entries[num_spectators++]; - entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language()); + entry.name.encode(spec_c->character_file()->disp.name.decode(spec_c->language()), c->language()); entry.description.encode(ep3_description_for_client(spec_c), c->language()); } } @@ -3721,7 +3719,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar if (player.is_human()) { try { auto pc = account_id_to_client.at(player.account_id); - entry.player_names[z].encode(pc->character()->disp.name.decode(pc->language()), lc->language()); + entry.player_names[z].encode(pc->character_file()->disp.name.decode(pc->language()), lc->language()); } catch (const out_of_range&) { entry.player_names[z].encode(player.player_name, lc->language()); } @@ -4211,7 +4209,7 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd; cmd.lobby_client_id = c->lobby_client_id; cmd.guild_card_number = c->login->account->account_id; - cmd.player_name = c->character()->disp.name; + cmd.player_name = c->character_file()->disp.name; if (team) { cmd.membership = team->base_membership_for_member(c->login->account->account_id); if (team->flag_data) { @@ -4353,7 +4351,8 @@ void send_team_reward_list(shared_ptr c, bool show_purchased) { auto s = c->require_server_state(); // Hide item rewards if the player's bank is full - bool show_item_rewards = show_purchased || (c->current_bank().num_items < 200); + auto bank = c->bank_file(); + bool show_item_rewards = show_purchased || (bank->items.size() < bank->max_items); vector entries; for (const auto& reward : s->team_index->reward_definitions()) { diff --git a/src/ServerState.cc b/src/ServerState.cc index 825055a2..73839761 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -72,8 +72,7 @@ ServerState::ServerState(const string& config_filename) thread_pool(make_unique()), bb_stream_files_cache(new FileContentsCache(3600000000ULL)), bb_system_cache(new FileContentsCache(3600000000ULL)), - gba_files_cache(new FileContentsCache(3600000000ULL)), - player_files_manager(make_shared(this->io_context)) {} + gba_files_cache(new FileContentsCache(3600000000ULL)) {} void ServerState::add_client_to_available_lobby(shared_ptr c) { shared_ptr added_to_lobby; @@ -1065,6 +1064,9 @@ void ServerState::load_config_early() { } catch (const out_of_range&) { } + this->bb_max_bank_items = this->config_json->get_int("BBMaxBankItems", 200); + this->bb_max_bank_meseta = this->config_json->get_int("BBMaxBankMeseta", 999999); + for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) { if (!this->item_stack_limits_tables[v_s]) { Version v = static_cast(v_s); diff --git a/src/ServerState.hh b/src/ServerState.hh index d0a29bda..ab95fa23 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -24,7 +24,6 @@ #include "LevelTable.hh" #include "Lobby.hh" #include "Menu.hh" -#include "PlayerFilesManager.hh" #include "Quest.hh" #include "TeamIndex.hh" #include "WordSelectTable.hh" @@ -205,6 +204,8 @@ struct ServerState : public std::enable_shared_from_this { std::array, NUM_VERSIONS> item_parameter_tables; std::shared_ptr item_translation_table; std::array, NUM_VERSIONS> item_stack_limits_tables; + size_t bb_max_bank_items = 200; + size_t bb_max_bank_meseta = 999999; std::shared_ptr mag_evolution_table_v1_v2; std::shared_ptr mag_evolution_table_v3; std::shared_ptr mag_evolution_table_v4; @@ -287,7 +288,6 @@ struct ServerState : public std::enable_shared_from_this { std::string pc_patch_server_message; std::string bb_patch_server_message; - std::shared_ptr player_files_manager; std::map> id_to_lobby; std::array, NUM_VERSIONS> public_lobby_search_orders; std::vector client_customization_public_lobby_search_order; diff --git a/src/ShellCommands.cc b/src/ShellCommands.cc index 8034a8e1..56b8efc5 100644 --- a/src/ShellCommands.cc +++ b/src/ShellCommands.cc @@ -975,7 +975,7 @@ asio::awaitable> fn_chat(ShellCommand::Args& args) { auto l = c->require_lobby(); for (auto& lc : l->clients) { if (lc) { - send_chat_message(lc, c->login->account->account_id, c->character()->disp.name.decode(c->language()), text, 0); + send_chat_message(lc, c->login->account->account_id, c->character_file()->disp.name.decode(c->language()), text, 0); } } } @@ -1008,7 +1008,8 @@ asio::awaitable> fn_wchat(ShellCommand::Args& args) { auto l = c->require_lobby(); for (auto& lc : l->clients) { if (lc) { - send_chat_message(lc, c->login->account->account_id, c->character()->disp.name.decode(c->language()), args.args, 0x40); + send_chat_message( + lc, c->login->account->account_id, c->character_file()->disp.name.decode(c->language()), args.args, 0x40); } } } diff --git a/system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s b/system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s new file mode 100644 index 00000000..a7cc2c56 --- /dev/null +++ b/system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s @@ -0,0 +1,102 @@ +# This patch changes the amount of items and Meseta that can be stored in the +# bank. If the bank item limit is increased beyond 200, this patch requires +# server support for extended bank data stored outside of the player's data. +# newserv has support for this, but you must set the BBBankItemLimit and +# BBBankMesetaLimit values in config.json to match the values used here. + +# As written, this changes the meseta limit to 2000000000 and the item limit to +# 1000. The meseta limit can be any value up to 2147483647, and the item limit +# can be any value up to 1321. To use different values than the defaults, first +# compute the data size as ((slot count * 0x18) + 8), then replace each value +# below appropriately. + +.meta name="More bank slots" +.meta description="" +.meta hide_from_patches_menu + +entry_ptr: +reloc0: + .offsetof start + +start: + .include WriteCodeBlocksBB + + .data 0x006C8C0F + .data 4 + .data 1000 # slot count + .data 0x006C8C4D + .data 4 + .data 1000 # slot count + .data 0x006C8B54 + .data 4 + .data 999 # slot count - 1 + .data 0x006C8B94 + .data 4 + .data 0x5DC0 # data size - 8 + .data 0x006C8D16 + .data 4 + .data 999 # slot count - 1 + .data 0x006C8E5E + .data 4 + .data 999 # slot count - 1 + .data 0x006C8F2C + .data 4 + .data 999 # slot count - 1 + .data 0x006C9016 + .data 4 + .data 0x5DB0 # data size - 0x18 + .data 0x006C9034 + .data 4 + .data 0x5DC0 # data size - 8 + .data 0x006C910D + .data 4 + .data 0x5DB0 # data size - 0x18 + .data 0x006C9129 + .data 4 + .data 0x5DC8 # data size + .data 0x006C9236 + .data 4 + .data 1000 # slot count + .data 0x006C924C + .data 4 + .data 999 # slot count - 1 + .data 0x006C9286 + .data 4 + .data 999 # slot count - 1 + .data 0x006C92FA + .data 4 + .data 1000 # slot count + .data 0x006C9883 + .data 4 + .data 1000 # slot count + .data 0x006C9A22 + .data 4 + .data 2000000000 # max meseta + .data 0x006CA2DB + .data 4 + .data 0x5DC8 # data size + .data 0x006CA303 + .data 4 + .data 1000 # slot count + .data 0x006CA37F + .data 4 + .data 0x5DC8 # data size + .data 0x006D7DAC + .data 4 + .data 1000 # slot count + .data 0x006D7DBD + .data 4 + .data 1000 # slot count + .data 0x006D7E14 + .data 4 + .data 1000 # slot count + .data 0x006D7BF5 + .data 4 + .data 1000 # slot count + + .data 0x006C8DBF + .data 2 + jmp +0x27 + + .data 0 + .data 0 diff --git a/system/config.example.json b/system/config.example.json index 03d5ba7b..e7f08d95 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -672,6 +672,13 @@ [0x040, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"], ], + // BB bank size. If you change either of these values, you must also add + // "BankSize" to BBRequiredPatches, and change the patch contents in + // system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s to + // reflect the counts you set here. + "BBMaxBankItems": 200, + "BBMaxBankMeseta": 999999, + // Item stack limits. Note that changing these does not affect the client's // behavior automatically - this only exists to allow the server to // understand the behavior of clients that are already patched with different