From b13e67d491ca5d1147a06c366c91c008bb965268 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 17 Sep 2024 21:54:56 -0700 Subject: [PATCH] split team membership struct from base BB system file --- src/ChatCommands.cc | 2 +- src/Client.cc | 70 ++++++++---------------------------------- src/CommandFormats.hh | 9 ++++-- src/ReceiveCommands.cc | 7 ++--- src/SaveFileFormats.cc | 47 +++++++++++++++++++++++++++- src/SaveFileFormats.hh | 26 +++++++++------- src/SendCommands.cc | 10 +++--- 7 files changed, 89 insertions(+), 82 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index beef9204..39645784 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1418,7 +1418,7 @@ static void server_command_edit(shared_ptr c, const std::string& args) { p->guild_card.language = new_language; auto sys = c->system_file(false); if (sys) { - sys->base.language = new_language; + sys->language = new_language; } } else if (tokens.at(0) == "secid" && cheats_allowed) { uint8_t secid = section_id_for_name(tokens.at(1)); diff --git a/src/Client.cc b/src/Client.cc index bc6a12e3..e73278c4 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -759,32 +759,24 @@ void Client::load_all_files() { if (this->character_data) { player_data_log.info("Using loaded character file %s", char_filename.c_str()); } else if (phosg::isfile(char_filename)) { - auto f = phosg::fopen_unique(char_filename, "rb"); - auto header = phosg::freadx(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"); - } - static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBFullSystemFile) == 0x3994, ".psochar size is incorrect"); - this->character_data = make_shared(phosg::freadx(f.get())); + auto psochar = load_psochar(char_filename, !this->system_data); + this->character_data = psochar.character_file; 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 + // If there was no .psosys file, use the system file from the .psochar // file instead if (!this->system_data) { - this->system_data = make_shared(phosg::freadx(f.get())); + if (!psochar.system_file) { + 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("Loaded system data from %s", char_filename.c_str()); } this->update_character_data_after_load(this->character_data); - this->system_data->base.language = this->language(); + this->system_data->language = this->language(); } else { player_data_log.info("Character file is missing: %s", char_filename.c_str()); @@ -813,7 +805,7 @@ void Client::load_all_files() { throw runtime_error("account data header is incorrect"); } if (!this->system_data) { - this->system_data = make_shared(nsa_data->system_file.base); + this->system_data = make_shared(nsa_data->system_file); files_manager->set_system(sys_filename, this->system_data); player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str()); } @@ -949,23 +941,7 @@ void Client::save_character_file( const string& filename, shared_ptr system, shared_ptr character) { - auto f = phosg::fopen_unique(filename, "wb"); - PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; - phosg::fwritex(f.get(), header); - phosg::fwritex(f.get(), *character); - phosg::fwritex(f.get(), *system); - // TODO: Technically, we should write the actual team membership struct to the - // file here, but that would cause Client to depend on Account, 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 Account 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; - phosg::fwritex(f.get(), empty_membership); + save_psochar(filename, system, character); player_data_log.info("Saved character file %s", filename.c_str()); } @@ -1007,18 +983,7 @@ void Client::save_guild_card_file() const { void Client::load_backup_character(uint32_t account_id, size_t index) { string filename = this->backup_character_filename(account_id, index, false); - auto f = phosg::fopen_unique(filename, "rb"); - auto header = phosg::freadx(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->character_data = make_shared(phosg::freadx(f.get())); + this->character_data = load_psochar(filename, false).character_file; this->update_character_data_after_load(this->character_data); this->v1_v2_last_reported_disp.reset(); } @@ -1106,18 +1071,7 @@ void Client::use_character_bank(int8_t index) { this->external_bank_character_index = index; player_data_log.info("Using loaded character file %s for external bank", filename.c_str()); } else if (phosg::isfile(filename)) { - auto f = phosg::fopen_unique(filename, "rb"); - auto header = phosg::freadx(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(phosg::freadx(f.get())); + this->external_bank_character = load_psochar(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); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 7edd452c..eaa13b5d 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3017,7 +3017,11 @@ struct S_TournamentEntryList_Ep3_E2 { } __packed_ws__(S_TournamentEntryList_Ep3_E2, 0x584); // E2 (S->C): Set system file contents (BB) -// See PSOBBFullSystemFile in SaveFileFormats.hh for format + +struct S_SyncSystemFile_BB_E2 { + PSOBBBaseSystemFile system_file; + PSOBBTeamMembership team_membership; +} __packed_ws__(S_SyncSystemFile_BB_E2, 0xAF0); // E3 (S->C): Game or tournament info (Episode 3) // The header.flag argument determines which fields are valid (and which panes @@ -3201,7 +3205,8 @@ struct C_CreateSpectatorTeam_Ep3_E7 { struct SC_SyncSaveFiles_BB_E7 { /* 0000 */ PSOBBCharacterFile char_file; - /* 2EA4 */ PSOBBFullSystemFile system_file; + /* 2EA4 */ PSOBBBaseSystemFile system_file; + /* 30DC */ PSOBBTeamMembership team_membership; /* 3994 */ } __packed_ws__(SC_SyncSaveFiles_BB_E7, 0x3994); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index fd08ebe5..e16cb0bc 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3798,13 +3798,12 @@ static void on_E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { p->challenge_records = cmd.char_file.challenge_records; p->battle_records = cmd.char_file.battle_records; p->death_count = cmd.char_file.death_count; - *c->system_file() = cmd.system_file.base; + *c->system_file() = cmd.system_file; } static void on_E2_BB(shared_ptr c, uint16_t, uint32_t, string& data) { - auto& cmd = check_size_t(data); - auto sys = c->system_file(); - *sys = cmd.base; + const auto& cmd = check_size_t(data); + *c->system_file() = cmd.system_file; c->save_system_file(); S_SystemFileCreated_00E1_BB out_cmd = {1}; diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 5e5342e9..1e5b6587 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -327,7 +327,7 @@ uint32_t PSOBBGuildCardFile::checksum() const { PSOBBBaseSystemFile::PSOBBBaseSystemFile() { // This field is based on 1/1/2000, not 1/1/1970, so adjust appropriately - this->base.creation_timestamp = (phosg::now() - 946684800000000ULL) / 1000000; + this->creation_timestamp = (phosg::now() - 946684800000000ULL) / 1000000; for (size_t z = 0; z < DEFAULT_KEY_CONFIG.size(); z++) { this->key_config[z] = DEFAULT_KEY_CONFIG[z]; } @@ -742,6 +742,51 @@ shared_ptr PSOBBCharacterFile::create_from_xb(const PSOXBCha return ret; } +LoadedPSOCHARFile load_psochar(const string& filename, bool load_system) { + auto f = phosg::fopen_unique(filename, "rb"); + auto header = phosg::freadx(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"); + } + static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership) == 0x3994, ".psochar size is incorrect"); + + LoadedPSOCHARFile ret; + ret.character_file = make_shared(phosg::freadx(f.get())); + if (load_system) { + ret.system_file = make_shared(phosg::freadx(f.get())); + } + return ret; +} + +void save_psochar( + const std::string& filename, + std::shared_ptr system, + std::shared_ptr character) { + auto f = phosg::fopen_unique(filename, "wb"); + PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; + phosg::fwritex(f.get(), header); + phosg::fwritex(f.get(), *character); + phosg::fwritex(f.get(), *system); + // TODO: Technically, we should write the actual team membership struct to + // the file here, but that would cause Client to depend on Account, 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 Account 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 wouldn't + // 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; + phosg::fwritex(f.get(), empty_membership); +} + PSODCV2CharacterFile PSOBBCharacterFile::to_dc_v2() const { uint8_t language = this->inventory.language; diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 24249b61..a45547c5 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -279,8 +279,7 @@ struct PSOBBMinimalSystemFile { /* 0114 */ } __packed_ws__(PSOBBMinimalSystemFile, 0x114); -struct PSOBBBaseSystemFile { - /* 0000 */ PSOBBMinimalSystemFile base; +struct PSOBBBaseSystemFile : PSOBBMinimalSystemFile { /* 0114 */ parray key_config; /* 0280 */ parray joystick_config; /* 02B8 */ @@ -288,14 +287,6 @@ struct PSOBBBaseSystemFile { PSOBBBaseSystemFile(); } __packed_ws__(PSOBBBaseSystemFile, 0x2B8); -struct PSOBBFullSystemFile { - /* 0000 */ PSOBBBaseSystemFile base; - /* 02B8 */ PSOBBTeamMembership team_membership; - /* 0AF0 */ - - PSOBBFullSystemFile() = default; -} __packed_ws__(PSOBBFullSystemFile, 0xAF0); - //////////////////////////////////////////////////////////////////////////////// // Character files @@ -757,6 +748,18 @@ struct PSOBBCharacterFile { void recompute_stats(std::shared_ptr level_table); } __packed_ws__(PSOBBCharacterFile, 0x2EA4); +struct LoadedPSOCHARFile { + std::shared_ptr system_file; // Null if load_system is false + std::shared_ptr character_file; // Never null + // Team membership is present in the file, but ignored by newserv +}; + +LoadedPSOCHARFile load_psochar(const std::string& filename, bool load_system); +void save_psochar( + const std::string& filename, + std::shared_ptr system, + std::shared_ptr character); + //////////////////////////////////////////////////////////////////////////////// // Guild Card files @@ -864,7 +867,8 @@ struct LegacySavedAccountDataBB { // .nsa file format /* 0000 */ pstring signature; /* 0040 */ parray blocked_senders; /* 00B8 */ PSOBBGuildCardFile guild_card_file; - /* D648 */ PSOBBFullSystemFile system_file; + /* D648 */ PSOBBBaseSystemFile system_file; + /* D880 */ PSOBBTeamMembership team_membership; /* E138 */ le_uint32_t unused = 0; /* E13C */ le_uint32_t option_flags = 0x00040058; /* E140 */ parray shortcuts; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 90f13eba..11e324db 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -619,8 +619,8 @@ void send_client_init_bb(shared_ptr c, uint32_t error_code) { void send_system_file_bb(shared_ptr c) { auto team = c->team(); - PSOBBFullSystemFile cmd; - cmd.base = *c->system_file(); + S_SyncSystemFile_BB_E2 cmd; + cmd.system_file = *c->system_file(); if (team) { cmd.team_membership = team->membership_for_member(c->login->account->account_id); } @@ -754,14 +754,14 @@ void send_complete_player_bb(shared_ptr c) { if (c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) { p->inventory.language = 1; p->guild_card.language = 1; - sys->base.language = 1; + sys->language = 1; } SC_SyncSaveFiles_BB_E7 cmd; cmd.char_file = *p; - cmd.system_file.base = *sys; + cmd.system_file = *sys; if (team) { - cmd.system_file.team_membership = team->membership_for_member(c->login->account->account_id); + cmd.team_membership = team->membership_for_member(c->login->account->account_id); } send_command_t(c, 0x00E7, 0x00000000, cmd); }