From dc7c3eb58cb1de55f378073b18aed2574e86bb81 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 11 May 2024 18:16:53 -0700 Subject: [PATCH] add DC v2 save file format --- src/Client.cc | 10 ++-- src/Client.hh | 4 +- src/CommandFormats.hh | 6 ++- src/PlayerFilesManager.cc | 4 +- src/PlayerFilesManager.hh | 6 +-- src/PlayerInventory.hh | 29 ++++++----- src/ReceiveCommands.cc | 4 +- src/SaveFileFormats.cc | 45 ++++++++++++++--- src/SaveFileFormats.hh | 101 +++++++++++++++++++++++++------------- src/SendCommands.cc | 4 +- 10 files changed, 146 insertions(+), 67 deletions(-) diff --git a/src/Client.cc b/src/Client.cc index b9545ba1..dd232c60 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -904,7 +904,7 @@ void Client::save_all() { } if (this->external_bank) { string filename = this->shared_bank_filename(); - save_object_file(filename, *this->external_bank); + save_object_file(filename, *this->external_bank); player_data_log.info("Saved shared bank file %s", filename.c_str()); } if (this->external_bank_character) { @@ -1002,7 +1002,7 @@ void Client::save_and_unload_character() { } } -PlayerBank& Client::current_bank() { +PlayerBank200& Client::current_bank() { if (this->external_bank) { return *this->external_bank; } else if (this->external_bank_character) { @@ -1018,7 +1018,7 @@ std::shared_ptr Client::current_bank_character() { void Client::use_default_bank() { if (this->external_bank) { string filename = this->shared_bank_filename(); - save_object_file(filename, *this->external_bank); + save_object_file(filename, *this->external_bank); this->external_bank.reset(); player_data_log.info("Detached shared bank %s", filename.c_str()); } @@ -1040,12 +1040,12 @@ bool Client::use_shared_bank() { player_data_log.info("Using loaded shared bank %s", filename.c_str()); return true; } else if (isfile(filename)) { - this->external_bank = make_shared(load_object_file(filename)); + this->external_bank = make_shared(load_object_file(filename)); files_manager->set_bank(filename, this->external_bank); player_data_log.info("Loaded shared bank %s", filename.c_str()); return true; } else { - this->external_bank = make_shared(); + this->external_bank = make_shared(); files_manager->set_bank(filename, this->external_bank); player_data_log.info("Created shared bank for %s", filename.c_str()); return false; diff --git a/src/Client.hh b/src/Client.hh index f1637b23..652c6808 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -381,7 +381,7 @@ public: void load_backup_character(uint32_t account_id, size_t index); void save_and_unload_character(); - PlayerBank& current_bank(); + PlayerBank200& current_bank(); 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(int8_t bb_character_index); @@ -398,7 +398,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; std::shared_ptr external_bank_character; int8_t external_bank_character_index; uint64_t last_play_time_update; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 010357c5..ce7ed984 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -7215,5 +7215,7 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 { // implement extended functionality. // 30 (C->S): Extended player info -// Requested with the GetExtendedPlayerInfo patch. -// Format is PSOGCCharacterFile::Character or PSOXBCharacterFileCharacter. +// Requested with the GetExtendedPlayerInfo patch. Format depends on version: +// DC v2: PSODCV2CharacterFile +// GC v3: PSOGCCharacterFile::Character +// XB v3: PSOXBCharacterFileCharacter diff --git a/src/PlayerFilesManager.cc b/src/PlayerFilesManager.cc index 6b1a6581..946e6115 100644 --- a/src/PlayerFilesManager.cc +++ b/src/PlayerFilesManager.cc @@ -66,7 +66,7 @@ std::shared_ptr PlayerFilesManager::get_guild_card(const std } } -std::shared_ptr PlayerFilesManager::get_bank(const std::string& filename) { +std::shared_ptr PlayerFilesManager::get_bank(const std::string& filename) { try { return this->loaded_bank_files.at(filename); } catch (const out_of_range&) { @@ -92,7 +92,7 @@ void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared } } -void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr file) { +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); } diff --git a/src/PlayerFilesManager.hh b/src/PlayerFilesManager.hh index 69dd4ed5..ae53c890 100644 --- a/src/PlayerFilesManager.hh +++ b/src/PlayerFilesManager.hh @@ -27,12 +27,12 @@ public: 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); + 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); + void set_bank(const std::string& filename, std::shared_ptr file); private: std::shared_ptr base; @@ -41,7 +41,7 @@ private: 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; + std::unordered_map> loaded_bank_files; static void clear_expired_files(evutil_socket_t fd, short events, void* ctx); }; diff --git a/src/PlayerInventory.hh b/src/PlayerInventory.hh index 61f6322b..5fa77cae 100644 --- a/src/PlayerInventory.hh +++ b/src/PlayerInventory.hh @@ -234,6 +234,7 @@ struct PlayerInventoryT { } size_t remove_all_items_of_type(uint8_t data1_0, int16_t data1_1 = -1) { + size_t write_offset = 0; for (size_t read_offset = 0; read_offset < this->num_items; read_offset++) { bool should_delete = ((this->items[read_offset].data.data1[0] == data1_0) && @@ -287,6 +288,7 @@ struct PlayerInventoryT { } operator PlayerInventoryT() const { + PlayerInventoryT ret; ret.num_items = this->num_items; ret.hp_from_materials = this->hp_from_materials; @@ -301,14 +303,14 @@ using PlayerInventoryBE = PlayerInventoryT; check_struct_size(PlayerInventory, 0x34C); check_struct_size(PlayerInventoryBE, 0x34C); -template +template struct PlayerBankT { using U32T = typename std::conditional::type; /* 0000 */ U32T num_items = 0; /* 0004 */ U32T meseta = 0; - /* 0008 */ parray, 200> items; - /* 12C8 */ + /* 0008 */ parray, SlotCount> items; + /* 05A8 for 60 items (v1/v2), 12C8 for 200 items (v3/v4) */ void add_item(const ItemData& item, const ItemData::StackLimits& limits) { uint32_t primary_identifier = item.primary_identifier(); @@ -341,7 +343,7 @@ struct PlayerBankT { } } - if (this->num_items >= 200) { + if (this->num_items >= SlotCount) { throw std::runtime_error("no free space in bank"); } auto& last_item = this->items[this->num_items]; @@ -352,6 +354,7 @@ struct PlayerBankT { } 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]; @@ -395,17 +398,21 @@ struct PlayerBankT { } } - operator PlayerBankT() const { - PlayerBankT ret; + template + operator PlayerBankT() const { + + PlayerBankT ret; ret.num_items = this->num_items.load(); ret.meseta = this->meseta.load(); - for (size_t z = 0; z < this->items.size(); z++) { + for (size_t z = 0; z < std::min(ret.items.size(), this->items.size()); z++) { ret.items[z] = this->items[z]; } return ret; } } __packed__; -using PlayerBank = PlayerBankT; -using PlayerBankBE = PlayerBankT; -check_struct_size(PlayerBank, 0x12C8); -check_struct_size(PlayerBankBE, 0x12C8); +using PlayerBank60 = PlayerBankT<60, false>; +using PlayerBank200 = PlayerBankT<200, false>; +using PlayerBank200BE = PlayerBankT<200, true>; +check_struct_size(PlayerBank60, 0x05A8); +check_struct_size(PlayerBank200, 0x12C8); +check_struct_size(PlayerBank200BE, 0x12C8); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 0ba977dc..e7651835 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3323,6 +3323,9 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { shared_ptr bb_char; switch (c->version()) { + case Version::DC_V2: + bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t(data)); + break; case Version::GC_V3: bb_char = PSOBBCharacterFile::create_from_gc(check_size_t(data)); break; @@ -3332,7 +3335,6 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { case Version::DC_NTE: case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: - case Version::DC_V2: case Version::PC_NTE: case Version::PC_V2: case Version::GC_NTE: diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index ea77f7f9..9dede319 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -418,6 +418,39 @@ shared_ptr PSOBBCharacterFile::create_from_preview( guild_card_number, language, preview.visual, preview.name.decode(language), level_table); } +shared_ptr PSOBBCharacterFile::create_from_dc_v2(const PSODCV2CharacterFile& dc) { + auto ret = make_shared(); + ret->inventory = dc.inventory; + ret->inventory.decode_from_client(Version::DC_V2); + uint8_t language = ret->inventory.language; + ret->disp = dc.disp.to_bb(language, language); + ret->creation_timestamp = dc.creation_timestamp; + ret->play_time_seconds = dc.play_time_seconds; + ret->option_flags = dc.option_flags; + ret->save_count = dc.save_count; + ret->quest_flags = dc.quest_flags; + ret->bank = dc.bank; + ret->guild_card = dc.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), dc.symbol_chats.size()); z++) { + auto& ret_sc = ret->symbol_chats[z]; + const auto& dc_sc = dc.symbol_chats[z]; + ret_sc.present = dc_sc.present.load(); + ret_sc.name.encode(dc_sc.name.decode(language), language); + ret_sc.spec = dc_sc.spec; + } + for (size_t z = 0; z < std::min(ret->shortcuts.size(), dc.shortcuts.size()); z++) { + ret->shortcuts[z] = dc.shortcuts[z].convert(language); + } + ret->battle_records = dc.battle_records; + ret->challenge_records = dc.challenge_records; + ret->tech_menu_shortcut_entries = dc.tech_menu_shortcut_entries; + for (size_t z = 0; z < 5; z++) { + ret->choice_search_config.entries[z].parent_choice_id = dc.choice_search_config[z * 2].load(); + ret->choice_search_config.entries[z].choice_id = dc.choice_search_config[z * 2 + 1].load(); + } + return ret; +} + shared_ptr PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) { auto ret = make_shared(); ret->inventory = gc.inventory; @@ -440,7 +473,7 @@ shared_ptr PSOBBCharacterFile::create_from_gc(const PSOGCCha ret_sc.spec = gc_sc.spec; } for (size_t z = 0; z < std::min(ret->shortcuts.size(), gc.shortcuts.size()); z++) { - ret->shortcuts[z] = gc.shortcuts[z].convert(language); + ret->shortcuts[z] = gc.shortcuts[z].convert(language); } ret->auto_reply.encode(gc.auto_reply.decode(language), language); ret->info_board.encode(gc.info_board.decode(language), language); @@ -482,7 +515,7 @@ shared_ptr PSOBBCharacterFile::create_from_xb(const PSOXBCha ret_sc.spec = xb_sc.spec; } for (size_t z = 0; z < std::min(ret->shortcuts.size(), xb.shortcuts.size()); z++) { - ret->shortcuts[z] = xb.shortcuts[z].convert(language); + ret->shortcuts[z] = xb.shortcuts[z].convert(language); } ret->auto_reply.encode(xb.auto_reply.decode(language), language); ret->info_board.encode(xb.info_board.decode(language), language); @@ -525,7 +558,7 @@ PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const { ret_sc.spec = gc_sc.spec; } for (size_t z = 0; z < std::min(ret.shortcuts.size(), this->shortcuts.size()); z++) { - ret.shortcuts[z] = this->shortcuts[z].convert(language); + ret.shortcuts[z] = this->shortcuts[z].convert(language); } ret.auto_reply.encode(this->auto_reply.decode(language), language); ret.info_board.encode(this->info_board.decode(language), language); @@ -570,7 +603,7 @@ PSOXBCharacterFileCharacter PSOBBCharacterFile::to_xb(uint64_t xb_user_id) const ret_sc.spec = gc_sc.spec; } for (size_t z = 0; z < std::min(ret.shortcuts.size(), this->shortcuts.size()); z++) { - ret.shortcuts[z] = this->shortcuts[z].convert(language); + ret.shortcuts[z] = this->shortcuts[z].convert(language); } ret.auto_reply.encode(this->auto_reply.decode(language), language); ret.info_board.encode(this->info_board.decode(language), language); @@ -605,7 +638,7 @@ SaveFileSymbolChatEntryBB PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry(u } // TODO: Eliminate duplication between this function and the parallel function -// in PlayerBank +// in PlayerBankT void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLimits& limits) { uint32_t primary_identifier = item.primary_identifier(); @@ -653,7 +686,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLim } // TODO: Eliminate code duplication between this function and the parallel -// function in PlayerBank +// function in PlayerBankT ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) { ItemData ret; diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 2c1da10d..77db185e 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -132,11 +132,11 @@ struct SaveFileSymbolChatEntryT { } __packed__; using SaveFileSymbolChatEntryPC = SaveFileSymbolChatEntryT; using SaveFileSymbolChatEntryGC = SaveFileSymbolChatEntryT; -using SaveFileSymbolChatEntryXB = SaveFileSymbolChatEntryT; +using SaveFileSymbolChatEntryDCXB = SaveFileSymbolChatEntryT; using SaveFileSymbolChatEntryBB = SaveFileSymbolChatEntryT; check_struct_size(SaveFileSymbolChatEntryPC, 0x70); check_struct_size(SaveFileSymbolChatEntryGC, 0x58); -check_struct_size(SaveFileSymbolChatEntryXB, 0x58); +check_struct_size(SaveFileSymbolChatEntryDCXB, 0x58); check_struct_size(SaveFileSymbolChatEntryBB, 0x68); template @@ -167,12 +167,12 @@ using WordSelectMessageBE = WordSelectMessageT; check_struct_size(WordSelectMessage, 0x1C); check_struct_size(WordSelectMessageBE, 0x1C); -template +template struct SaveFileChatShortcutEntryT { using U32T = std::conditional_t; union Definition { - pstring text; + pstring text; WordSelectMessageT word_select; SymbolChatT symbol_chat; @@ -184,14 +184,14 @@ struct SaveFileChatShortcutEntryT { } } __packed__; - /* GC:BB */ - /* 00:00 */ U32T type; // 1 = text, 2 = word select, 3 = symbol chat - /* 04:04 */ Definition definition; - /* 54:A4 */ + /* DC:GC:BB */ + /* 00:00:00 */ U32T type; // 1 = text, 2 = word select, 3 = symbol chat + /* 04:04:04 */ Definition definition; + /* 40:54:A4 */ - template - SaveFileChatShortcutEntryT convert(uint8_t language) const { - SaveFileChatShortcutEntryT ret; + template + SaveFileChatShortcutEntryT convert(uint8_t language) const { + SaveFileChatShortcutEntryT ret; ret.type = this->type.load(); switch (ret.type) { case 1: @@ -209,13 +209,49 @@ struct SaveFileChatShortcutEntryT { return ret; } } __packed__; -using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT; -using SaveFileShortcutEntryXB = SaveFileChatShortcutEntryT; -using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT; +using SaveFileShortcutEntryDC = SaveFileChatShortcutEntryT; +using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT; +using SaveFileShortcutEntryXB = SaveFileChatShortcutEntryT; +using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT; +check_struct_size(SaveFileShortcutEntryDC, 0x40); check_struct_size(SaveFileShortcutEntryGC, 0x54); check_struct_size(SaveFileShortcutEntryXB, 0x54); check_struct_size(SaveFileShortcutEntryBB, 0xA4); +struct PSODCV2CharacterFile { + // See PSOGCCharacterFile::Character for descriptions of fields' meanings. + /* 0000:---- */ PlayerInventory inventory; + /* 034C:---- */ PlayerDispDataDCPCV3 disp; + /* 041C:0000 */ le_uint32_t flags = 0; + /* 0420:0004 */ le_uint32_t creation_timestamp = 0; + /* 0424:0008 */ le_uint32_t signature = 0xA205B064; + /* 0428:000C */ le_uint32_t play_time_seconds = 0; + /* 042C:0010 */ le_uint32_t option_flags = 0x00040058; + /* 0430:0014 */ le_uint32_t save_count = 0; + /* 0434:0018 */ pstring ppp_username; + /* 0450:0034 */ pstring ppp_password; + /* 0460:0044 */ QuestFlags quest_flags; + /* 0660:0244 */ PlayerBank60 bank; + /* 0C08:07EC */ GuildCardDC guild_card; + /* 0C85:0869 */ parray unknown_s1; // Probably actually unused + /* 0C88:086C */ parray symbol_chats; + /* 10A8:0C8C */ parray shortcuts; + /* 15A8:118C */ pstring v1_serial_number; + /* 15B8:119C */ pstring v1_access_key; + /* 15C8:11AC */ PlayerRecordsBattle battle_records; + /* 15E0:11C4 */ PlayerRecordsChallengeDC challenge_records; + /* 1680:1264 */ parray tech_menu_shortcut_entries; + // The Choice Search config is stored here as 32-bit integers, even though + // it's always represented with 16-bit integers in the C0 command. The order + // of the entries here is the same (that is, the first two of these ints are + // entries[0], the second two are entries[1], etc.). + /* 16A8:128C */ parray choice_search_config; + /* 16D0:12B4 */ parray unknown_a2; + /* 16D4:12B8 */ pstring v2_serial_number; + /* 16E4:12C8 */ pstring v2_access_key; + /* 16F4:12D8 */ +} __packed_ws__(PSODCV2CharacterFile, 0x16F4); + struct PSOGCCharacterFile { /* 00000 */ be_uint32_t checksum; struct Character { @@ -257,11 +293,11 @@ struct PSOGCCharacterFile { // R = Map direction (0 = non-fixed; 1 = fixed) /* 042C:0010 */ be_uint32_t option_flags = 0x00040058; /* 0430:0014 */ be_uint32_t save_count = 0; - /* 0434:0018 */ parray unknown_a2; - /* 0450:0034 */ parray unknown_a3; + /* 0434:0018 */ pstring ppp_username; + /* 0450:0034 */ pstring ppp_password; /* 0460:0044 */ QuestFlags quest_flags; /* 0660:0244 */ be_uint32_t death_count = 0; - /* 0664:0248 */ PlayerBankBE bank; + /* 0664:0248 */ PlayerBank200BE bank; /* 192C:1510 */ GuildCardGCBE guild_card; /* 19BC:15A0 */ parray symbol_chats; /* 1DDC:19C0 */ parray shortcuts; @@ -307,19 +343,17 @@ struct PSOGCEp3CharacterFile { // this field mean. /* 042C:0010 */ be_uint32_t option_flags; /* 0430:0014 */ be_uint32_t save_count; - /* 0434:0018 */ parray unknown_a2; - /* 0450:0034 */ parray unknown_a3; + /* 0434:0018 */ pstring ppp_username; + /* 0450:0034 */ pstring ppp_password; // seq_vars is an array of 8192 bits, which contain all the Episode 3 quest // progress flags. This includes things like which maps are unlocked, which // NPC decks are unlocked, and whether the player has a VIP card or not. /* 0460:0044 */ parray seq_vars; /* 0860:0444 */ be_uint32_t death_count; - // The following three fields appear to actually be a PlayerBank structure - // with only 4 item slots instead of 200. They presumably didn't completely - // remove the bank in Ep3 because they would have to change too much code. - /* 0864:0448 */ be_uint32_t num_bank_items; - /* 0868:044C */ be_uint32_t bank_meseta; - /* 086C:0450 */ parray bank_items; + // Curiously, Episode 3 characters do have item banks, but there are only 4 + // item slots. Sega presumably didn't completely remove the bank in Ep3 + // because they would have to change too much code. + /* 0864:0448 */ PlayerBankT<4, true> bank; /* 08CC:04B0 */ GuildCardGCBE guild_card; /* 095C:0540 */ parray symbol_chats; /* 0D7C:0960 */ parray chat_shortcuts; @@ -410,13 +444,13 @@ struct PSOXBCharacterFileCharacter { /* 0428:000C */ le_uint32_t play_time_seconds = 0; /* 042C:0010 */ le_uint32_t option_flags = 0x00040058; /* 0430:0014 */ le_uint32_t save_count = 0; - /* 0434:0018 */ parray unknown_a2; - /* 0450:0034 */ parray unknown_a3; + /* 0434:0018 */ pstring ppp_username; + /* 0450:0034 */ pstring ppp_password; /* 0460:0044 */ QuestFlags quest_flags; /* 0660:0244 */ le_uint32_t death_count = 0; - /* 0664:0248 */ PlayerBank bank; + /* 0664:0248 */ PlayerBank200 bank; /* 192C:1510 */ GuildCardXB guild_card; - /* 1B58:173C */ parray symbol_chats; + /* 1B58:173C */ parray symbol_chats; /* 1F78:1B5C */ parray shortcuts; /* 24B8:209C */ pstring auto_reply; /* 2518:20FC */ pstring info_board; @@ -759,7 +793,7 @@ struct PSOBBCharacterFile { /* 04F0 */ le_uint32_t save_count = 0; /* 04F4 */ QuestFlags quest_flags; /* 06F4 */ le_uint32_t death_count = 0; - /* 06F8 */ PlayerBank bank; + /* 06F8 */ PlayerBank200 bank; /* 19C0 */ GuildCardBB guild_card; /* 1AC8 */ le_uint32_t unknown_a3 = 0; /* 1ACC */ parray symbol_chats; @@ -795,8 +829,9 @@ struct PSOBBCharacterFile { uint8_t language, const PlayerDispDataBBPreview& preview, std::shared_ptr level_table); - static std::shared_ptr create_from_gc(const PSOGCCharacterFile::Character& gc_char); - static std::shared_ptr create_from_xb(const PSOXBCharacterFileCharacter& xb_char); + static std::shared_ptr create_from_dc_v2(const PSODCV2CharacterFile& dc); + static std::shared_ptr create_from_gc(const PSOGCCharacterFile::Character& gc); + static std::shared_ptr create_from_xb(const PSOXBCharacterFileCharacter& xb); void add_item(const ItemData& item, const ItemData::StackLimits& limits); ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits); @@ -856,7 +891,7 @@ struct LegacySavedPlayerDataBB { // .nsc file format /* 0028 */ PlayerRecordsBattle battle_records; /* 0040 */ PlayerDispDataBBPreview preview; /* 00BC */ pstring auto_reply; - /* 0214 */ PlayerBank bank; + /* 0214 */ PlayerBank200 bank; /* 14DC */ PlayerRecordsChallengeBB challenge_records; /* 161C */ PlayerDispDataBB disp; /* 17AC */ pstring guild_card_description; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index dd70c2dc..29ffa454 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2338,11 +2338,11 @@ void send_self_leave_notification(shared_ptr c) { } void send_get_player_info(shared_ptr c, bool request_extended) { - // TODO: Support extended player info on other versions. + // TODO: Support extended player info on Ep3 JP and NTE if (request_extended && !c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) && !c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY) && - ((c->version() == Version::GC_V3) || (c->version() == Version::XB_V3))) { + ((c->version() == Version::DC_V2) || (c->version() == Version::GC_V3) || (c->version() == Version::XB_V3))) { auto s = c->require_server_state(); prepare_client_for_patches(c, [wc = weak_ptr(c)]() { auto c = wc.lock();