add DC v2 save file format

This commit is contained in:
Martin Michelsen
2024-05-11 18:16:53 -07:00
parent a0126bd6b5
commit dc7c3eb58c
10 changed files with 146 additions and 67 deletions
+5 -5
View File
@@ -904,7 +904,7 @@ void Client::save_all() {
}
if (this->external_bank) {
string filename = this->shared_bank_filename();
save_object_file<PlayerBank>(filename, *this->external_bank);
save_object_file<PlayerBank200>(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<PSOBBCharacterFile> Client::current_bank_character() {
void Client::use_default_bank() {
if (this->external_bank) {
string filename = this->shared_bank_filename();
save_object_file<PlayerBank>(filename, *this->external_bank);
save_object_file<PlayerBank200>(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<PlayerBank>(load_object_file<PlayerBank>(filename));
this->external_bank = make_shared<PlayerBank200>(load_object_file<PlayerBank200>(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<PlayerBank>();
this->external_bank = make_shared<PlayerBank200>();
files_manager->set_bank(filename, this->external_bank);
player_data_log.info("Created shared bank for %s", filename.c_str());
return false;
+2 -2
View File
@@ -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<PSOBBCharacterFile> 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<PSOBBCharacterFile> overlay_character_data;
std::shared_ptr<PSOBBCharacterFile> character_data;
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank> external_bank;
std::shared_ptr<PlayerBank200> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
int8_t external_bank_character_index;
uint64_t last_play_time_update;
+4 -2
View File
@@ -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
+2 -2
View File
@@ -66,7 +66,7 @@ std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std
}
}
std::shared_ptr<PlayerBank> PlayerFilesManager::get_bank(const std::string& filename) {
std::shared_ptr<PlayerBank200> 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<PlayerBank> file) {
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename);
}
+3 -3
View File
@@ -27,12 +27,12 @@ public:
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
std::shared_ptr<PSOBBCharacterFile> get_character(const std::string& filename);
std::shared_ptr<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
std::shared_ptr<PlayerBank> get_bank(const std::string& filename);
std::shared_ptr<PlayerBank200> get_bank(const std::string& filename);
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank> file);
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
private:
std::shared_ptr<struct event_base> base;
@@ -41,7 +41,7 @@ private:
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
std::unordered_map<std::string, std::shared_ptr<PlayerBank>> loaded_bank_files;
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_files;
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
};
+18 -11
View File
@@ -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<!IsBigEndian>() const {
PlayerInventoryT<!IsBigEndian> ret;
ret.num_items = this->num_items;
ret.hp_from_materials = this->hp_from_materials;
@@ -301,14 +303,14 @@ using PlayerInventoryBE = PlayerInventoryT<true>;
check_struct_size(PlayerInventory, 0x34C);
check_struct_size(PlayerInventoryBE, 0x34C);
template <bool IsBigEndian>
template <size_t SlotCount, bool IsBigEndian>
struct PlayerBankT {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* 0000 */ U32T num_items = 0;
/* 0004 */ U32T meseta = 0;
/* 0008 */ parray<PlayerBankItemT<IsBigEndian>, 200> items;
/* 12C8 */
/* 0008 */ parray<PlayerBankItemT<IsBigEndian>, 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<!IsBigEndian>() const {
PlayerBankT<!IsBigEndian> ret;
template <size_t DestSlotCount, bool DestIsBigEndian>
operator PlayerBankT<DestSlotCount, DestIsBigEndian>() const {
PlayerBankT<DestSlotCount, DestIsBigEndian> 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<size_t>(ret.items.size(), this->items.size()); z++) {
ret.items[z] = this->items[z];
}
return ret;
}
} __packed__;
using PlayerBank = PlayerBankT<false>;
using PlayerBankBE = PlayerBankT<true>;
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);
+3 -1
View File
@@ -3323,6 +3323,9 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
shared_ptr<PSOBBCharacterFile> bb_char;
switch (c->version()) {
case Version::DC_V2:
bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t<PSODCV2CharacterFile>(data));
break;
case Version::GC_V3:
bb_char = PSOBBCharacterFile::create_from_gc(check_size_t<PSOGCCharacterFile::Character>(data));
break;
@@ -3332,7 +3335,6 @@ static void on_30(shared_ptr<Client> 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:
+39 -6
View File
@@ -418,6 +418,39 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
guild_card_number, language, preview.visual, preview.name.decode(language), level_table);
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_dc_v2(const PSODCV2CharacterFile& dc) {
auto ret = make_shared<PSOBBCharacterFile>();
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<size_t>(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<size_t>(ret->shortcuts.size(), dc.shortcuts.size()); z++) {
ret->shortcuts[z] = dc.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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> PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) {
auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = gc.inventory;
@@ -440,7 +473,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc(const PSOGCCha
ret_sc.spec = gc_sc.spec;
}
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), gc.shortcuts.size()); z++) {
ret->shortcuts[z] = gc.shortcuts[z].convert<false, TextEncoding::UTF16>(language);
ret->shortcuts[z] = gc.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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> PSOBBCharacterFile::create_from_xb(const PSOXBCha
ret_sc.spec = xb_sc.spec;
}
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), xb.shortcuts.size()); z++) {
ret->shortcuts[z] = xb.shortcuts[z].convert<false, TextEncoding::UTF16>(language);
ret->shortcuts[z] = xb.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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<size_t>(ret.shortcuts.size(), this->shortcuts.size()); z++) {
ret.shortcuts[z] = this->shortcuts[z].convert<true, TextEncoding::MARKED>(language);
ret.shortcuts[z] = this->shortcuts[z].convert<true, TextEncoding::MARKED, 0x50>(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<size_t>(ret.shortcuts.size(), this->shortcuts.size()); z++) {
ret.shortcuts[z] = this->shortcuts[z].convert<false, TextEncoding::MARKED>(language);
ret.shortcuts[z] = this->shortcuts[z].convert<false, TextEncoding::MARKED, 0x50>(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;
+68 -33
View File
@@ -132,11 +132,11 @@ struct SaveFileSymbolChatEntryT {
} __packed__;
using SaveFileSymbolChatEntryPC = SaveFileSymbolChatEntryT<false, TextEncoding::UTF16, 0x18>;
using SaveFileSymbolChatEntryGC = SaveFileSymbolChatEntryT<true, TextEncoding::MARKED, 0x18>;
using SaveFileSymbolChatEntryXB = SaveFileSymbolChatEntryT<false, TextEncoding::MARKED, 0x18>;
using SaveFileSymbolChatEntryDCXB = SaveFileSymbolChatEntryT<false, TextEncoding::MARKED, 0x18>;
using SaveFileSymbolChatEntryBB = SaveFileSymbolChatEntryT<false, TextEncoding::UTF16, 0x14>;
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 <bool IsBigEndian>
@@ -167,12 +167,12 @@ using WordSelectMessageBE = WordSelectMessageT<true>;
check_struct_size(WordSelectMessage, 0x1C);
check_struct_size(WordSelectMessageBE, 0x1C);
template <bool IsBigEndian, TextEncoding Encoding>
template <bool IsBigEndian, TextEncoding Encoding, size_t MaxChars>
struct SaveFileChatShortcutEntryT {
using U32T = std::conditional_t<IsBigEndian, be_uint32_t, le_uint32_t>;
union Definition {
pstring<Encoding, 0x50> text;
pstring<Encoding, MaxChars> text;
WordSelectMessageT<IsBigEndian> word_select;
SymbolChatT<IsBigEndian> 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 <bool RetIsBigEndian, TextEncoding RetEncoding>
SaveFileChatShortcutEntryT<RetIsBigEndian, RetEncoding> convert(uint8_t language) const {
SaveFileChatShortcutEntryT<RetIsBigEndian, RetEncoding> ret;
template <bool RetIsBigEndian, TextEncoding RetEncoding, size_t RetMaxSize>
SaveFileChatShortcutEntryT<RetIsBigEndian, RetEncoding, RetMaxSize> convert(uint8_t language) const {
SaveFileChatShortcutEntryT<RetIsBigEndian, RetEncoding, RetMaxSize> ret;
ret.type = this->type.load();
switch (ret.type) {
case 1:
@@ -209,13 +209,49 @@ struct SaveFileChatShortcutEntryT {
return ret;
}
} __packed__;
using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT<true, TextEncoding::MARKED>;
using SaveFileShortcutEntryXB = SaveFileChatShortcutEntryT<false, TextEncoding::MARKED>;
using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT<false, TextEncoding::UTF16>;
using SaveFileShortcutEntryDC = SaveFileChatShortcutEntryT<false, TextEncoding::MARKED, 0x3C>;
using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT<true, TextEncoding::MARKED, 0x50>;
using SaveFileShortcutEntryXB = SaveFileChatShortcutEntryT<false, TextEncoding::MARKED, 0x50>;
using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT<false, TextEncoding::UTF16, 0x50>;
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<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
/* 0460:0044 */ QuestFlags quest_flags;
/* 0660:0244 */ PlayerBank60 bank;
/* 0C08:07EC */ GuildCardDC guild_card;
/* 0C85:0869 */ parray<uint8_t, 3> unknown_s1; // Probably actually unused
/* 0C88:086C */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 10A8:0C8C */ parray<SaveFileShortcutEntryDC, 20> shortcuts;
/* 15A8:118C */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 15B8:119C */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 15C8:11AC */ PlayerRecordsBattle battle_records;
/* 15E0:11C4 */ PlayerRecordsChallengeDC challenge_records;
/* 1680:1264 */ parray<le_uint16_t, 20> 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<le_uint32_t, 10> choice_search_config;
/* 16D0:12B4 */ parray<uint8_t, 4> unknown_a2;
/* 16D4:12B8 */ pstring<TextEncoding::ASCII, 0x10> v2_serial_number;
/* 16E4:12C8 */ pstring<TextEncoding::ASCII, 0x10> 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<uint8_t, 0x1C> unknown_a2;
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a3;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> 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<SaveFileSymbolChatEntryGC, 12> symbol_chats;
/* 1DDC:19C0 */ parray<SaveFileShortcutEntryGC, 20> 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<uint8_t, 0x1C> unknown_a2;
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a3;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> 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<uint8_t, 0x400> 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<PlayerBankItemBE, 4> 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<SaveFileSymbolChatEntryGC, 12> symbol_chats;
/* 0D7C:0960 */ parray<SaveFileShortcutEntryGC, 20> 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<uint8_t, 0x1C> unknown_a2;
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a3;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> 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<SaveFileSymbolChatEntryXB, 12> symbol_chats;
/* 1B58:173C */ parray<SaveFileSymbolChatEntryDCXB, 12> symbol_chats;
/* 1F78:1B5C */ parray<SaveFileShortcutEntryXB, 16> shortcuts;
/* 24B8:209C */ pstring<TextEncoding::MARKED, 0xAC> auto_reply;
/* 2518:20FC */ pstring<TextEncoding::MARKED, 0xAC> 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<SaveFileSymbolChatEntryBB, 0x0C> symbol_chats;
@@ -795,8 +829,9 @@ struct PSOBBCharacterFile {
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
static std::shared_ptr<PSOBBCharacterFile> create_from_gc(const PSOGCCharacterFile::Character& gc_char);
static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb_char);
static std::shared_ptr<PSOBBCharacterFile> create_from_dc_v2(const PSODCV2CharacterFile& dc);
static std::shared_ptr<PSOBBCharacterFile> create_from_gc(const PSOGCCharacterFile::Character& gc);
static std::shared_ptr<PSOBBCharacterFile> 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<TextEncoding::UTF16, 0x00AC> auto_reply;
/* 0214 */ PlayerBank bank;
/* 0214 */ PlayerBank200 bank;
/* 14DC */ PlayerRecordsChallengeBB challenge_records;
/* 161C */ PlayerDispDataBB disp;
/* 17AC */ pstring<TextEncoding::UTF16, 0x0058> guild_card_description;
+2 -2
View File
@@ -2338,11 +2338,11 @@ void send_self_leave_notification(shared_ptr<Client> c) {
}
void send_get_player_info(shared_ptr<Client> 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<Client>(c)]() {
auto c = wc.lock();