implement shared bank

This commit is contained in:
Martin Michelsen
2023-12-04 16:59:03 -08:00
parent 01b83044dc
commit c25569c688
8 changed files with 252 additions and 71 deletions
+172 -27
View File
@@ -66,19 +66,35 @@ std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std
}
}
std::shared_ptr<PlayerBank> 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<PSOBBBaseSystemFile> file) {
if (!this->loaded_system_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded");
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
if (!this->loaded_character_files.emplace(filename, file).second) {
throw runtime_error("character file already loaded");
throw runtime_error("character file already loaded: " + filename);
}
}
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
if (!this->loaded_guild_card_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded");
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename);
}
}
@@ -96,6 +112,10 @@ void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx)
if (num_deleted) {
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_bank_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
}
}
ClientGameData::ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager)
@@ -111,10 +131,24 @@ ClientGameData::ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager
ClientGameData::~ClientGameData() {
if (!this->bb_username.empty() && this->character_data.get()) {
this->save_character_file();
this->save_all();
}
}
const string& ClientGameData::get_bb_username() const {
return this->bb_username;
}
void ClientGameData::set_bb_username(const string& bb_username) {
// Make sure bb_username is filename-safe
for (char ch : bb_username) {
if (!isalnum(ch) && (ch != '-') && (ch != '_')) {
throw runtime_error("invalid characters in username");
}
}
this->bb_username = bb_username;
}
void ClientGameData::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_ptr<const LevelTable> level_table) {
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character(true, false));
@@ -268,14 +302,17 @@ string ClientGameData::system_filename() const {
return string_printf("system/players/system_%s.psosys", this->bb_username.c_str());
}
string ClientGameData::character_filename() const {
string ClientGameData::character_filename(int8_t index) const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have character data");
}
if (this->bb_character_index < 0) {
if (index < 0) {
index = this->bb_character_index;
}
if (index < 0) {
throw logic_error("character index is not set");
}
return string_printf("system/players/player_%s_%hhd.psochar", this->bb_username.c_str(), this->bb_character_index);
return string_printf("system/players/player_%s_%hhd.psochar", this->bb_username.c_str(), index);
}
string ClientGameData::guild_card_filename() const {
@@ -285,6 +322,13 @@ string ClientGameData::guild_card_filename() const {
return string_printf("system/players/guild_cards_%s.psocard", this->bb_username.c_str());
}
string ClientGameData::shared_bank_filename() const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have shared bank files");
}
return string_printf("system/players/shared_bank_%s.psobank", this->bb_username.c_str());
}
string ClientGameData::legacy_account_filename() const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have legacy account data");
@@ -356,7 +400,7 @@ void ClientGameData::load_all_files() {
throw runtime_error("incorrect flag in character file header");
}
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->files_manager->set_character(this->character_filename(), this->character_data);
this->files_manager->set_character(char_filename, this->character_data);
player_data_log.info("Loaded character data from %s", char_filename.c_str());
// If there was no .psosys file, load the system file from the .psochar
@@ -473,6 +517,29 @@ void ClientGameData::load_all_files() {
}
}
void ClientGameData::save_all() {
if (this->system_data) {
this->save_system_file();
}
if (this->character_data) {
this->save_character_file();
}
if (this->guild_card_data) {
this->save_guild_card_file();
}
if (this->external_bank) {
string filename = this->shared_bank_filename();
save_object_file<PlayerBank>(filename, *this->external_bank);
player_data_log.info("Saved shared bank file %s", filename.c_str());
}
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 ClientGameData::save_system_file() const {
if (!this->system_data) {
throw logic_error("no system file loaded");
@@ -482,6 +549,30 @@ void ClientGameData::save_system_file() const {
player_data_log.info("Saved system file %s", filename.c_str());
}
void ClientGameData::save_character_file(
const string& filename,
shared_ptr<const PSOBBBaseSystemFile> system,
shared_ptr<const PSOBBCharacterFile> character) {
auto f = fopen_unique(filename, "wb");
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
fwritex(f.get(), header);
fwritex(f.get(), *character);
fwritex(f.get(), *system);
// TODO: Technically, we should write the actual team membership struct to the
// file here, but that would cause ClientGameData to depend on License, which
// it currently does not. This data doesn't matter at all for correctness
// within newserv, since it ignores this data entirely and instead generates
// the membership struct from the team ID in the License and the team's state.
// So, writing correct data here would mostly be for compatibility with other
// PSO servers. But if the other server is newserv, then this data would be
// used anyway, and if it's not, then it would presumably have a different set
// of teams with a different set of team IDs anyway, so the membership struct
// here would be useless either way.
static const PSOBBTeamMembership empty_membership;
fwritex(f.get(), empty_membership);
player_data_log.info("Saved character file %s", filename.c_str());
}
void ClientGameData::save_character_file() {
if (!this->system_data.get()) {
throw logic_error("no system file loaded");
@@ -500,25 +591,7 @@ void ClientGameData::save_character_file() {
this->last_play_time_update = t;
}
string filename = this->character_filename();
auto f = fopen_unique(filename, "wb");
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
fwritex(f.get(), header);
fwritex(f.get(), *this->character_data);
fwritex(f.get(), *this->system_data);
// TODO: Technically, we should write the actual team membership struct to the
// file here, but that would cause ClientGameData to depend on License, which
// it currently does not. This data doesn't matter at all for correctness
// within newserv, since it ignores this data entirely and instead generates
// the membership struct from the team ID in the License and the team's state.
// So, writing correct data here would mostly be for compatibility with other
// PSO servers. But if the other server is newserv, then this data would be
// used anyway, and if it's not, then it would presumably have a different set
// of teams with a different set of team IDs anyway, so the membership struct
// here would be useless either way.
static const PSOBBTeamMembership empty_membership;
fwritex(f.get(), empty_membership);
player_data_log.info("Saved character file %s", filename.c_str());
this->save_character_file(this->character_filename(), this->system_data, this->character_data);
}
void ClientGameData::save_guild_card_file() const {
@@ -529,3 +602,75 @@ void ClientGameData::save_guild_card_file() const {
save_object_file(filename, *this->guild_card_data);
player_data_log.info("Saved Guild Card file %s", filename.c_str());
}
PlayerBank& ClientGameData::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;
}
std::shared_ptr<PSOBBCharacterFile> ClientGameData::current_bank_character() {
return this->external_bank_character ? this->external_bank_character : this->character();
}
void ClientGameData::use_default_bank() {
if (this->external_bank) {
string filename = this->shared_bank_filename();
save_object_file<PlayerBank>(filename, *this->external_bank);
this->external_bank.reset();
player_data_log.info("Detached shared bank %s", filename.c_str());
}
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("Detached character %s from bank", filename.c_str());
}
}
bool ClientGameData::use_shared_bank() {
this->use_default_bank();
string filename = this->shared_bank_filename();
if (isfile(filename)) {
this->external_bank = make_shared<PlayerBank>(load_object_file<PlayerBank>(filename));
player_data_log.info("Loaded shared bank %s", filename.c_str());
return true;
} else {
this->external_bank = make_shared<PlayerBank>();
player_data_log.info("Created shared bank for %s", filename.c_str());
return false;
}
}
void ClientGameData::use_character_bank(int8_t index) {
this->use_default_bank();
if (index != this->bb_character_index) {
string filename = this->character_filename(index);
this->external_bank_character = this->files_manager->get_character(filename);
if (this->external_bank_character) {
this->external_bank_character_index = index;
player_data_log.info("Using loaded character file %s for external bank", filename.c_str());
} else if (isfile(filename)) {
auto f = fopen_unique(filename, "rb");
auto header = freadx<PSOCommandHeaderBB>(f.get());
if (header.size != 0x399C) {
throw runtime_error("incorrect size in character file header");
}
if (header.command != 0x00E7) {
throw runtime_error("incorrect command in character file header");
}
if (header.flag != 0x00000000) {
throw runtime_error("incorrect flag in character file header");
}
this->external_bank_character = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->external_bank_character_index = index;
this->files_manager->set_character(filename, this->external_bank_character);
player_data_log.info("Loaded character data from %s for external bank", filename.c_str());
} else {
throw runtime_error("character does not exist");
}
}
}