From 025fc154d43cc6c849d7c07fb3f27ba57de16487 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 29 Oct 2023 10:42:57 -0700 Subject: [PATCH] refine some BB structures --- src/CommandFormats.hh | 58 +++++++++++++++++---------- src/Player.hh | 5 ++- src/PlayerSubordinates.cc | 9 ----- src/PlayerSubordinates.hh | 33 --------------- src/ReceiveCommands.cc | 84 +++++++++++++++++++++------------------ src/SaveFileFormats.cc | 9 +++++ src/SaveFileFormats.hh | 49 ++++++++++++++++++++++- src/SendCommands.cc | 21 +++++----- src/SendCommands.hh | 2 +- 9 files changed, 155 insertions(+), 115 deletions(-) diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 302fc6fc..7b72579b 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -11,6 +11,7 @@ #include "Episode3/PlayerStateSubordinates.hh" #include "PSOProtocol.hh" #include "Player.hh" +#include "SaveFileFormats.hh" #include "Text.hh" #define __packed__ __attribute__((packed)) @@ -2428,7 +2429,7 @@ struct C_CreateGame_BB_C1 : C_CreateGame { // C2 (C->S): Set choice search parameters (DCv2 and later versions) // Internal name: PutChoiceList // Server does not respond. -// The ChoiceSearchConfig structure is defined in Player.hh. +// The ChoiceSearchConfig structure is defined in PlayerSubordinates.hh. struct C_ChoiceSearchSelections_DC_C2_C3 : ChoiceSearchConfig { } __packed__; @@ -2890,8 +2891,8 @@ struct S_TournamentEntryList_GC_Ep3_E2 { parray entries; } __packed__; -// E2 (S->C): Team and key config (BB) -// See KeyAndTeamConfigBB in Player.hh for format +// E2 (S->C): Set system file contents (BB) +// See PSOBBSystemFile in SaveFileFormats.hh for format // E3 (S->C): Game or tournament info (Episode 3) // The header.flag argument determines which fields are valid (and which panes @@ -3042,7 +3043,9 @@ struct S_ClientInit_BB_00E6 { le_uint32_t guild_card_number = 0; le_uint32_t team_id = 0; ClientConfigBB cfg; - le_uint32_t caps = 0x00000102; + uint8_t can_create_team = 1; + uint8_t episode_4_unlocked = 1; + parray unused; } __packed__; // E7 (C->S): Create spectator team (Episode 3) @@ -3086,7 +3089,7 @@ struct SC_SyncCharacterSaveFile_BB_00E7 { /* 2DF8 */ parray tech_menu_config; // player /* 2E20 */ parray unknown_a6; /* 2E4C */ parray quest_data2; // player - /* 2EA4 */ KeyAndTeamConfigBB key_config; // account + /* 2EA4 */ PSOBBSystemFile system_file; // account /* 3994 */ } __attribute__((packed)); @@ -3171,7 +3174,7 @@ struct S_GuildCardChecksumResponse_BB_02E8 { // Server should send the guild card file data using DC commands. // 04E8 (C->S): Add guild card -// Format is GuildCardBB (see Player.hh) +// Format is GuildCardBB (see PlayerSubordinates.hh) // 05E8 (C->S): Delete guild card @@ -3182,10 +3185,10 @@ struct C_DeleteGuildCard_BB_05E8_08E8 { // 06E8 (C->S): Update (overwrite) guild card // Note: This command is also sent when the player writes a comment on their own // guild card. -// Format is GuildCardBB (see Player.hh) +// Format is GuildCardBB (see PlayerSubordinates.hh) // 07E8 (C->S): Add blocked user -// Format is GuildCardBB (see Player.hh) +// Format is GuildCardBB (see PlayerSubordinates.hh) // 08E8 (C->S): Delete blocked user // Same format as 05E8. @@ -3444,19 +3447,33 @@ struct C_LeaveCharacterSelect_BB_00EC { // disbanded because the target game ends. // ED (C->S): Update account data (BB) -// There are several subcommands (noted in the union below) that each update a +// There are several subcommands (noted in the structs below) that each update a // specific kind of account data. // TODO: Actually define these structures and don't just treat them as raw data -union C_UpdateAccountData_BB_ED { - le_uint32_t option = 0; // 01ED - parray symbol_chats; // 02ED - parray chat_shortcuts; // 03ED - parray key_config; // 04ED - parray pad_config; // 05ED - parray tech_menu; // 06ED - parray customize; // 07ED - parray challenge_battle_config; // 08ED +struct C_UpdateAccountOptionFlags_BB_01ED { + le_uint32_t option_flags = 0; +} __packed__; +struct C_UpdateAccountSymbolChats_BB_02ED { + parray symbol_chats; +} __packed__; +struct C_UpdateAccountChatShortcuts_BB_03ED { + parray chat_shortcuts; +} __packed__; +struct C_UpdateAccountKeyConfig_BB_04ED { + parray key_config; +} __packed__; +struct C_UpdateAccountPadConfig_BB_05ED { + parray pad_config; +} __packed__; +struct C_UpdateAccountTechMenu_BB_06ED { + parray tech_menu; +} __packed__; +struct C_UpdateAccountCustomizeMenu_BB_07ED { + parray customize; +} __packed__; +struct C_UpdateAccountChallengeAndBattleConfig_BB_08ED { + parray challenge_battle_config; } __packed__; // EE: Trade cards (Episode 3) @@ -3533,8 +3550,9 @@ struct S_SetShutdownCommand_BB_01EF { } __packed__; // F0 (S->C): Force update player lobby data (BB) -// Format is PlayerLobbyDataBB (in Player.hh). This command overwrites the lobby -// data for the player given by .client_id without reloading the game or lobby. +// Format is PlayerLobbyDataBB (in PlayerSubordinates.hh). This command +// overwrites the lobby data for the player given by .client_id without +// reloading the game or lobby. // F1: Invalid command // F2: Invalid command diff --git a/src/Player.hh b/src/Player.hh index a6913eb1..a5b99986 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -14,6 +14,7 @@ #include "ItemNameIndex.hh" #include "LevelTable.hh" #include "PlayerSubordinates.hh" +#include "SaveFileFormats.hh" #include "Text.hh" #include "Version.hh" @@ -83,8 +84,8 @@ enum AccountFlag { struct SavedAccountDataBB { // .nsa file format pstring signature; parray blocked_senders; - GuildCardFileBB guild_cards; - KeyAndTeamConfigBB key_config; + PSOBBGuildCardFile guild_card_file; + PSOBBSystemFile system_file; le_uint32_t newserv_flags; le_uint32_t option_flags; parray shortcuts; diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 7eaf9b14..aa43927c 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -221,15 +221,6 @@ void GuildCardBB::clear() { this->char_class = 0; } -void GuildCardEntryBB::clear() { - this->data.clear(); - this->unknown_a1.clear(0); -} - -uint32_t GuildCardFileBB::checksum() const { - return crc32(this, sizeof(*this)); -} - void PlayerBank::load(const string& filename) { *this = player_files_cache.get_obj_or_load(filename).obj; for (uint32_t x = 0; x < this->num_items; x++) { diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 262df4bc..3bf180e8 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -236,39 +236,6 @@ struct GuildCardBB { void clear(); } __attribute__((packed)); -// an entry in the BB guild card file -struct GuildCardEntryBB { - GuildCardBB data; - pstring comment; - parray unknown_a1; - - void clear(); -} __attribute__((packed)); - -// the format of the BB guild card file -struct GuildCardFileBB { - parray unknown_a1; - GuildCardBB blocked[0x1C]; - parray unknown_a2; - GuildCardEntryBB entries[0x69]; - - uint32_t checksum() const; -} __attribute__((packed)); - -struct KeyAndTeamConfigBB { - parray unknown_a1; // 0000 - parray key_config; // 0114 - parray joystick_config; // 0280 - le_uint32_t guild_card_number = 0; // 02B8 - le_uint32_t team_id = 0; // 02BC - le_uint64_t team_info = 0; // 02C0 - le_uint16_t team_privilege_level = 0; // 02C8 - le_uint16_t reserved = 0; // 02CA - pstring team_name; // 02CC - parray team_flag; // 02EC - le_uint32_t team_rewards = 0; // 0AEC -} __attribute__((packed)); - struct PlayerLobbyDataPC { le_uint32_t player_tag = 0; le_uint32_t guild_card = 0; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index a3bb2924..bad13632 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2905,7 +2905,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { static void on_00E0_BB(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); - send_team_and_key_config_bb(c); + send_system_file_bb(c); c->game_data.account()->newserv_flags &= ~AccountFlag::IN_DRESSING_ROOM; c->log.info("Cleared dressing room flag for account"); } @@ -2941,12 +2941,12 @@ static void on_00E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { - constexpr size_t max_count = sizeof(GuildCardFileBB::entries) / sizeof(GuildCardEntryBB); - constexpr size_t max_blocked = sizeof(GuildCardFileBB::blocked) / sizeof(GuildCardBB); + constexpr size_t max_count = sizeof(PSOBBGuildCardFile::entries) / sizeof(PSOBBGuildCardFile::Entry); + constexpr size_t max_blocked = sizeof(PSOBBGuildCardFile::blocked) / sizeof(GuildCardBB); switch (command) { case 0x01E8: { // Check guild card file checksum const auto& cmd = check_size_t(data); - uint32_t checksum = c->game_data.account()->guild_cards.checksum(); + uint32_t checksum = c->game_data.account()->guild_card_file.checksum(); c->log.info("(Guild card file) Server checksum = %08" PRIX32 ", client checksum = %08" PRIX32, checksum, cmd.checksum.load()); S_GuildCardChecksumResponse_BB_02E8 response = { @@ -2960,7 +2960,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& break; case 0x04E8: { // Add guild card auto& new_gc = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; for (size_t z = 0; z < max_count; z++) { if (!gcf.entries[z].data.present) { gcf.entries[z].data = new_gc; @@ -2974,7 +2974,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& } case 0x05E8: { // Delete guild card auto& cmd = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; for (size_t z = 0; z < max_count; z++) { if (gcf.entries[z].data.guild_card_number == cmd.guild_card_number) { c->log.info("Deleted guild card %" PRIu32 " at position %zu", @@ -2990,7 +2990,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& } case 0x06E8: { // Update guild card auto& new_gc = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; for (size_t z = 0; z < max_count; z++) { if (gcf.entries[z].data.guild_card_number == new_gc.guild_card_number) { gcf.entries[z].data = new_gc; @@ -3002,7 +3002,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& } case 0x07E8: { // Add blocked user auto& new_gc = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; for (size_t z = 0; z < max_blocked; z++) { if (!gcf.blocked[z].present) { gcf.blocked[z] = new_gc; @@ -3017,7 +3017,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& } case 0x08E8: { // Delete blocked user auto& cmd = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; for (size_t z = 0; z < max_blocked; z++) { if (gcf.blocked[z].guild_card_number == cmd.guild_card_number) { c->log.info("Deleted blocked guild card %" PRIu32 " at position %zu", @@ -3035,7 +3035,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& } case 0x09E8: { // Write comment auto& cmd = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; for (size_t z = 0; z < max_count; z++) { if (gcf.entries[z].data.guild_card_number == cmd.guild_card_number) { gcf.entries[z].comment = cmd.comment; @@ -3048,7 +3048,7 @@ static void on_00E8_BB(shared_ptr c, uint16_t command, uint32_t, string& } case 0x0AE8: { // Move guild card in list auto& cmd = check_size_t(data); - auto& gcf = c->game_data.account()->guild_cards; + auto& gcf = c->game_data.account()->guild_card_file; if (cmd.position >= max_count) { throw invalid_argument("invalid new position"); } @@ -3157,37 +3157,47 @@ static void on_00E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } static void on_xxED_BB(shared_ptr c, uint16_t command, uint32_t, string& data) { - const auto* cmd = reinterpret_cast(data.data()); - switch (command) { - case 0x01ED: - check_size_v(data.size(), sizeof(cmd->option)); - c->game_data.account()->option_flags = cmd->option; + case 0x01ED: { + const auto& cmd = check_size_t(data); + c->game_data.account()->option_flags = cmd.option_flags; break; - case 0x02ED: - check_size_v(data.size(), sizeof(cmd->symbol_chats)); - c->game_data.account()->symbol_chats = cmd->symbol_chats; + } + case 0x02ED: { + const auto& cmd = check_size_t(data); + c->game_data.account()->symbol_chats = cmd.symbol_chats; break; - case 0x03ED: - check_size_v(data.size(), sizeof(cmd->chat_shortcuts)); - c->game_data.account()->shortcuts = cmd->chat_shortcuts; + } + case 0x03ED: { + const auto& cmd = check_size_t(data); + c->game_data.account()->shortcuts = cmd.chat_shortcuts; break; - case 0x04ED: - check_size_v(data.size(), sizeof(cmd->key_config)); - c->game_data.account()->key_config.key_config = cmd->key_config; + } + case 0x04ED: { + const auto& cmd = check_size_t(data); + c->game_data.account()->system_file.key_config = cmd.key_config; break; - case 0x05ED: - check_size_v(data.size(), sizeof(cmd->pad_config)); - c->game_data.account()->key_config.joystick_config = cmd->pad_config; + } + case 0x05ED: { + const auto& cmd = check_size_t(data); + c->game_data.account()->system_file.joystick_config = cmd.pad_config; break; - case 0x06ED: - check_size_v(data.size(), sizeof(cmd->tech_menu)); - c->game_data.player()->tech_menu_config = cmd->tech_menu; + } + case 0x06ED: { + const auto& cmd = check_size_t(data); + c->game_data.player()->tech_menu_config = cmd.tech_menu; break; - case 0x07ED: - check_size_v(data.size(), sizeof(cmd->customize)); - c->game_data.player()->disp.config = cmd->customize; + } + case 0x07ED: { + const auto& cmd = check_size_t(data); + c->game_data.player()->disp.config = cmd.customize; break; + } + case 0x08ED: { + check_size_t(data); + // TODO: Save this data appropriately + break; + } default: throw invalid_argument("unknown account command"); } @@ -3208,10 +3218,8 @@ static void on_00E7_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } static void on_00E2_BB(shared_ptr c, uint16_t, uint32_t, string& data) { - // Some clients have only a uint32_t at the end for team rewards - auto& cmd = check_size_t(data, - sizeof(KeyAndTeamConfigBB) - 4, sizeof(KeyAndTeamConfigBB)); - c->game_data.account()->key_config = cmd; + auto& cmd = check_size_t(data); + c->game_data.account()->system_file = cmd; } static void on_89(shared_ptr c, uint16_t, uint32_t flag, string& data) { diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index ad032ebb..cc103eb6 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -180,3 +180,12 @@ Image PSOGCSnapshotFile::decode_image() const { } return ret; } + +void PSOBBGuildCardFile::Entry::clear() { + this->data.clear(); + this->unknown_a1.clear(0); +} + +uint32_t PSOBBGuildCardFile::checksum() const { + return crc32(this, sizeof(*this)); +} diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index db966c30..78dafdda 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -10,8 +10,9 @@ #include #include +#include "Episode3/DataIndexes.hh" #include "PSOEncryption.hh" -#include "Player.hh" +#include "PlayerSubordinates.hh" #include "Text.hh" struct ShuffleTables { @@ -117,6 +118,52 @@ struct PSOGCEp3SystemFile { /* 012C */ } __attribute__((packed)); +struct PSOBBSystemFileBase { + /* 0000 */ be_uint32_t checksum = 0; + /* 0004 */ be_int16_t music_volume = -50; + /* 0006 */ int8_t sound_volume = 0; + /* 0007 */ uint8_t language = 0; + /* 0008 */ be_uint32_t server_time_delta_frames = 1728000; + /* 000C */ be_uint16_t udp_behavior = 0; // 0 = auto, 1 = on, 2 = off + /* 000E */ be_uint16_t surround_sound_enabled = 0; + /* 0010 */ parray event_flags; + /* 0110 */ le_uint32_t creation_timestamp = 0; + /* 0114 */ +} __attribute__((packed)); + +struct PSOBBSystemFile { + /* 0000 */ PSOBBSystemFileBase base; + /* 0114 */ parray key_config; + /* 0280 */ parray joystick_config; + /* 02B8 */ le_uint32_t guild_card_number = 0; + /* 02BC */ le_uint32_t team_id = 0; + /* 02C0 */ le_uint64_t team_info = 0; + /* 02C8 */ le_uint16_t team_privilege_level = 0; + /* 02CA */ le_uint16_t reserved = 0; + /* 02CC */ pstring team_name; + /* 02EC */ parray team_flag; + /* 0AEC */ le_uint32_t team_rewards = 0; + /* 0AF0 */ +} __attribute__((packed)); + +struct PSOBBGuildCardFile { + struct Entry { + /* 0000 */ GuildCardBB data; + /* 0108 */ pstring comment; + /* 01B8 */ parray unknown_a1; + /* 01BC */ + + void clear(); + } __attribute__((packed)); + + PSOBBSystemFileBase system_file; + parray blocked; + parray unknown_a2; + parray entries; + + uint32_t checksum() const; +} __attribute__((packed)); + struct PSOGCSaveFileSymbolChatEntry { /* 00 */ be_uint32_t present; /* 04 */ pstring name; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 87f6dc5a..6a5904da 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -476,12 +476,13 @@ void send_client_init_bb(shared_ptr c, uint32_t error) { cmd.guild_card_number = c->license->serial_number; cmd.team_id = static_cast(random_object()); cmd.cfg = c->export_config_bb(); - cmd.caps = 0x00000102; + cmd.can_create_team = 1; + cmd.episode_4_unlocked = 1; send_command_t(c, 0x00E6, 0x00000000, cmd); } -void send_team_and_key_config_bb(shared_ptr c) { - send_command_t(c, 0x00E2, 0x00000000, c->game_data.account()->key_config); +void send_system_file_bb(shared_ptr c) { + send_command_t(c, 0x00E2, 0x00000000, c->game_data.account()->system_file); } void send_player_preview_bb(shared_ptr c, uint8_t player_index, @@ -499,26 +500,24 @@ void send_player_preview_bb(shared_ptr c, uint8_t player_index, } void send_guild_card_header_bb(shared_ptr c) { - uint32_t checksum = c->game_data.account()->guild_cards.checksum(); - S_GuildCardHeader_BB_01DC cmd = {1, sizeof(GuildCardFileBB), checksum}; + uint32_t checksum = c->game_data.account()->guild_card_file.checksum(); + S_GuildCardHeader_BB_01DC cmd = {1, sizeof(PSOBBGuildCardFile), checksum}; send_command_t(c, 0x01DC, 0x00000000, cmd); } void send_guild_card_chunk_bb(shared_ptr c, size_t chunk_index) { size_t chunk_offset = chunk_index * 0x6800; - if (chunk_offset >= sizeof(GuildCardFileBB)) { + if (chunk_offset >= sizeof(PSOBBGuildCardFile)) { throw logic_error("attempted to send chunk beyond end of guild card file"); } S_GuildCardFileChunk_02DC cmd; - size_t data_size = min( - sizeof(GuildCardFileBB) - chunk_offset, sizeof(cmd.data)); - + size_t data_size = min(sizeof(PSOBBGuildCardFile) - chunk_offset, sizeof(cmd.data)); cmd.unknown = 0; cmd.chunk_index = chunk_index; cmd.data.assign_range( - reinterpret_cast(&c->game_data.account()->guild_cards) + chunk_offset, + reinterpret_cast(&c->game_data.account()->guild_card_file) + chunk_offset, data_size, 0); send_command(c, 0x02DC, 0x00000000, &cmd, sizeof(cmd) - sizeof(cmd.data) + data_size); @@ -643,7 +642,7 @@ void send_complete_player_bb(shared_ptr c) { cmd.tech_menu_config = player->tech_menu_config; cmd.unknown_a6.clear(0); cmd.quest_data2 = player->quest_data2; - cmd.key_config = account->key_config; + cmd.system_file = account->system_file; send_command_t(c, 0x00E7, 0x00000000, cmd); } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 1e24cbf0..f82e8992 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -160,7 +160,7 @@ void send_pc_console_split_reconnect( uint16_t console_port); void send_client_init_bb(std::shared_ptr c, uint32_t error); -void send_team_and_key_config_bb(std::shared_ptr c); +void send_system_file_bb(std::shared_ptr c); void send_player_preview_bb(std::shared_ptr c, uint8_t player_index, const PlayerDispDataBBPreview* preview); void send_accept_client_checksum_bb(std::shared_ptr c);