#pragma once #include #include #include #include #include #include #include #include "LevelTable.hh" #include "ItemData.hh" #include "Version.hh" #include "Text.hh" #include "Episode3/DataIndex.hh" struct PlayerBankItem; struct PlayerInventoryItem { // 0x1C bytes le_uint32_t present; le_uint32_t flags; // 8 = equipped ItemData data; PlayerInventoryItem(); PlayerInventoryItem(const PlayerBankItem&); void clear(); } __attribute__((packed)); struct PlayerBankItem { // 0x18 bytes ItemData data; le_uint16_t amount; le_uint16_t show_flags; PlayerBankItem(); PlayerBankItem(const PlayerInventoryItem&); void clear(); } __attribute__((packed)); struct PlayerInventory { // 0x34C bytes uint8_t num_items; uint8_t hp_materials_used; uint8_t tp_materials_used; uint8_t language; PlayerInventoryItem items[30]; PlayerInventory(); size_t find_item(uint32_t item_id); } __attribute__((packed)); struct PlayerBank { // 0x12C8 bytes le_uint32_t num_items; le_uint32_t meseta; PlayerBankItem items[200]; void load(const std::string& filename); void save(const std::string& filename, bool save_to_filesystem) const; bool switch_with_file(const std::string& save_filename, const std::string& load_filename); void add_item(const PlayerBankItem& item); PlayerBankItem remove_item(uint32_t item_id, uint32_t amount); size_t find_item(uint32_t item_id); } __attribute__((packed)); struct PendingItemTrade { uint8_t other_client_id; bool confirmed; // true if client has sent a D2 command std::vector items; }; struct PendingCardTrade { uint8_t other_client_id; bool confirmed; // true if client has sent an EE D2 command std::vector> card_to_count; }; struct PlayerDispDataBB; struct PlayerDispDataDCPCV3 { // 0xD0 bytes PlayerStats stats; parray unknown_a1; le_uint32_t level; le_uint32_t experience; le_uint32_t meseta; ptext name; uint64_t unknown_a2; le_uint32_t name_color; uint8_t extra_model; parray unused; le_uint32_t name_color_checksum; uint8_t section_id; uint8_t char_class; uint8_t v2_flags; uint8_t version; le_uint32_t v1_flags; le_uint16_t costume; le_uint16_t skin; le_uint16_t face; le_uint16_t head; le_uint16_t hair; le_uint16_t hair_r; le_uint16_t hair_g; le_uint16_t hair_b; le_float proportion_x; le_float proportion_y; parray config; parray technique_levels; // Note: This struct has a default constructor because it's used in a command // that has a fixed-size array. If we didn't define this constructor, the // trivial fields in that array's members would be uninitialized, and we could // send uninitialized memory to the client. PlayerDispDataDCPCV3() noexcept; void enforce_v2_limits(); PlayerDispDataBB to_bb() const; } __attribute__((packed)); // BB player preview format struct PlayerDispDataBBPreview { le_uint32_t experience; le_uint32_t level; ptext guild_card; uint64_t unknown_a2; le_uint32_t name_color; uint8_t extra_model; parray unused; le_uint32_t name_color_checksum; uint8_t section_id; uint8_t char_class; uint8_t v2_flags; uint8_t version; le_uint32_t v1_flags; le_uint16_t costume; le_uint16_t skin; le_uint16_t face; le_uint16_t head; le_uint16_t hair; le_uint16_t hair_r; le_uint16_t hair_g; le_uint16_t hair_b; le_float proportion_x; le_float proportion_y; ptext name; uint32_t play_time; PlayerDispDataBBPreview() noexcept; } __attribute__((packed)); // BB player appearance and stats data struct PlayerDispDataBB { PlayerStats stats; parray unknown_a1; le_uint32_t level; le_uint32_t experience; le_uint32_t meseta; ptext guild_card; uint64_t unknown_a2; le_uint32_t name_color; // ARGB8888 uint8_t extra_model; parray unused; le_uint32_t name_color_checksum; uint8_t section_id; uint8_t char_class; uint8_t v2_flags; uint8_t version; le_uint32_t v1_flags; le_uint16_t costume; le_uint16_t skin; le_uint16_t face; le_uint16_t head; le_uint16_t hair; le_uint16_t hair_r; le_uint16_t hair_g; le_uint16_t hair_b; le_float proportion_x; le_float proportion_y; ptext name; le_uint32_t play_time; uint32_t unknown_a3; parray config; parray technique_levels; PlayerDispDataBB() noexcept; inline void enforce_v2_limits() { } PlayerDispDataDCPCV3 to_dcpcv3() const; PlayerDispDataBBPreview to_preview() const; void apply_preview(const PlayerDispDataBBPreview&); void apply_dressing_room(const PlayerDispDataBBPreview&); } __attribute__((packed)); // TODO: Is this the same for XB as it is for GC? (This struct is based on the // GC format) struct GuildCardV3 { le_uint32_t player_tag; le_uint32_t guild_card_number; ptext name; ptext description; uint8_t present; // should be 1 uint8_t language; uint8_t section_id; uint8_t char_class; GuildCardV3() noexcept; } __attribute__((packed)); // BB guild card format struct GuildCardBB { le_uint32_t guild_card_number; ptext name; ptext team_name; ptext description; uint8_t present; // should be 1 if guild card entry exists uint8_t language; uint8_t section_id; uint8_t char_class; GuildCardBB() noexcept; void clear(); } __attribute__((packed)); // an entry in the BB guild card file struct GuildCardEntryBB { GuildCardBB data; ptext 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; // 02B8 le_uint32_t team_id; // 02BC le_uint64_t team_info; // 02C0 le_uint16_t team_privilege_level; // 02C8 le_uint16_t reserved; // 02CA ptext team_name; // 02CC parray team_flag; // 02EC le_uint32_t team_rewards; // 0AEC } __attribute__((packed)); struct PlayerLobbyDataPC { le_uint32_t player_tag = 0; le_uint32_t guild_card = 0; // There's a strange behavior (bug? "feature"?) in Episode 3 where the start // button does nothing in the lobby (hence you can't "quit game") if the // client's IP address is zero. So, we fill it in with a fake nonzero value to // avoid this behavior, and to be consistent, we make IP addresses fake and // nonzero on all other versions too. be_uint32_t ip_address = 0x7F000001; le_uint32_t client_id = 0; ptext name; void clear(); } __attribute__((packed)); struct PlayerLobbyDataDCGC { le_uint32_t player_tag = 0; le_uint32_t guild_card = 0; be_uint32_t ip_address = 0x7F000001; le_uint32_t client_id = 0; ptext name; void clear(); } __attribute__((packed)); struct XBNetworkLocation { le_uint32_t internal_ipv4_address = 0x0A0A0A0A; le_uint32_t external_ipv4_address = 0x23232323; le_uint16_t port = 9100; parray mac_address = 0x77; parray unknown_a1; le_uint64_t account_id = 0xFFFFFFFFFFFFFFFF; parray unknown_a2; void clear(); } __attribute__((packed)); struct PlayerLobbyDataXB { le_uint32_t player_tag = 0; le_uint32_t guild_card = 0; XBNetworkLocation netloc; le_uint32_t client_id = 0; ptext name; void clear(); } __attribute__((packed)); struct PlayerLobbyDataBB { le_uint32_t player_tag = 0; le_uint32_t guild_card = 0; // This field is a guess; the official builds didn't use this, but all other // versions have it be_uint32_t ip_address = 0x7F000001; parray unknown_a1; le_uint32_t client_id = 0; ptext name; le_uint32_t unknown_a2 = 0; void clear(); } __attribute__((packed)); struct PlayerChallengeDataV3 { le_uint32_t client_id; struct { le_uint16_t unknown_a1; parray unknown_a2; // Possibly unused parray unknown_a3; struct { parray unknown_a1; le_uint16_t unknown_a2; parray unknown_a3; parray unknown_a4; parray unknown_a5; } __attribute__((packed)) unknown_a4; // 0x50 bytes struct { parray unknown_a1; parray unknown_a2; } __attribute__((packed)) unknown_a5; // 0x10 bytes struct UnknownPair { le_uint32_t unknown_a1; le_uint32_t unknown_a2; } __attribute__((packed)); parray unknown_a6; // 0x18 bytes parray unknown_a7; } __attribute__((packed)) unknown_a1; // 0x100 bytes // On Episode 3, unknown_a2[0] is win count, [1] is loss count, and [4] is // disconnect count parray unknown_a2; parray unknown_a3; } __attribute__((packed)); // 0x11C bytes struct PlayerChallengeDataBB { le_uint32_t client_id; parray unknown_a1; } __attribute__((packed)); template struct ChoiceSearchConfig { // 0 = enabled, 1 = disabled. Unused for command C3 le_uint32_t choice_search_disabled = 0; struct Entry { ItemIDT parent_category_id = 0; ItemIDT category_id = 0; } __attribute__((packed)); parray entries; } __attribute__((packed)); struct PSOPlayerDataDCPC { // For command 61 PlayerInventory inventory; PlayerDispDataDCPCV3 disp; } __attribute__((packed)); struct PSOPlayerDataV3 { // For command 61 PlayerInventory inventory; PlayerDispDataDCPCV3 disp; PlayerChallengeDataV3 challenge_data; ChoiceSearchConfig choice_search_config; ptext info_board; parray blocked_senders; le_uint32_t auto_reply_enabled; // The auto-reply message can be up to 0x200 bytes. If it's shorter than that, // the client truncates the command after the first zero byte (rounded up to // the next 4-byte boundary). char auto_reply[0]; } __attribute__((packed)); struct PSOPlayerDataGCEp3 { // For command 61 PlayerInventory inventory; PlayerDispDataDCPCV3 disp; PlayerChallengeDataV3 challenge_data; ChoiceSearchConfig choice_search_config; ptext info_board; parray blocked_senders; le_uint32_t auto_reply_enabled; ptext auto_reply; Episode3::PlayerConfig ep3_config; } __attribute__((packed)); struct PSOPlayerDataBB { // For command 61 PlayerInventory inventory; PlayerDispDataBB disp; PlayerChallengeDataBB challenge_data; ChoiceSearchConfig choice_search_config; ptext info_board; parray blocked_senders; le_uint32_t auto_reply_enabled; char16_t auto_reply[0]; } __attribute__((packed)); struct PlayerBB { // Used in 00E7 command PlayerInventory inventory; // 0000-034C; player PlayerDispDataBB disp; // 034C-04DC; player parray unknown; // 04DC-04EC; not saved le_uint32_t option_flags; // 04EC-04F0; account parray quest_data1; // 04F0-06F8; player PlayerBank bank; // 06F8-19C0; player le_uint32_t guild_card_number; // 19C0-19C4; player ptext name; // 19C4-19F4; player ptext team_name; // 19F4-1A14; player ptext guild_card_description; // 1A14-1AC4; player uint8_t reserved1; // 1AC4-1AC5; player uint8_t reserved2; // 1AC5-1AC6; player uint8_t section_id; // 1AC6-1AC7; player uint8_t char_class; // 1AC7-1AC8; player le_uint32_t unknown3; // 1AC8-1ACC; not saved parray symbol_chats; // 1ACC-1FAC; account parray shortcuts; // 1FAC-29EC; account ptext auto_reply; // 29EC-2B44; player ptext info_board; // 2B44-2C9C; player parray unknown5; // 2C9C-2CB8; not saved parray challenge_data; // 2CB8-2DF8; player parray tech_menu_config; // 2DF8-2E20; player parray unknown6; // 2E20-2E4C; not saved parray quest_data2; // 2E4C-2EA4; player KeyAndTeamConfigBB key_config; // 2EA4-3994; account } __attribute__((packed)); struct SavedPlayerDataBB { // .nsc file format ptext signature; PlayerDispDataBBPreview preview; ptext auto_reply; PlayerBank bank; parray challenge_data; PlayerDispDataBB disp; ptext guild_card_description; ptext info_board; PlayerInventory inventory; parray quest_data1; parray quest_data2; parray tech_menu_config; void add_item(const PlayerInventoryItem& item); PlayerInventoryItem remove_item( uint32_t item_id, uint32_t amount, bool allow_meseta_overdraft); void print_inventory(FILE* stream) const; } __attribute__((packed)); enum AccountFlag { IN_DRESSING_ROOM = 0x00000001, }; struct SavedAccountDataBB { // .nsa file format ptext signature; parray blocked_senders; GuildCardFileBB guild_cards; KeyAndTeamConfigBB key_config; le_uint32_t newserv_flags; le_uint32_t option_flags; parray shortcuts; parray symbol_chats; ptext team_name; } __attribute__((packed)); class ClientGameData { private: std::shared_ptr account_data; std::shared_ptr player_data; uint64_t last_play_time_update; public: uint32_t guild_card_number; bool should_update_play_time; // The following fields are not saved, and are only used in certain situations // Null unless the client is within the trade sequence (D0-D4 or EE commands) std::unique_ptr pending_item_trade; std::unique_ptr pending_card_trade; // Null unless the client is Episode 3 and has sent its config already std::shared_ptr ep3_config; // These are only used if the client is BB std::string bb_username; size_t bb_player_index; PlayerInventoryItem identify_result; std::array, 3> shop_contents; bool should_save; ClientGameData(); ~ClientGameData(); std::shared_ptr account(bool should_load = true); std::shared_ptr player(bool should_load = true); std::shared_ptr account() const; std::shared_ptr player() const; std::string account_data_filename() const; std::string player_data_filename() const; static std::string player_template_filename(uint8_t char_class); void create_player( const PlayerDispDataBBPreview& preview, std::shared_ptr level_table); void load_account_data(); void save_account_data() const; void load_player_data(); // Note: This function is not const because it updates the player's play time. void save_player_data(); void import_player(const PSOPlayerDataDCPC& pd); void import_player(const PSOPlayerDataV3& pd); void import_player(const PSOPlayerDataBB& pd); // Note: this function is not const because it can cause player and account // data to be loaded PlayerBB export_player_bb(); }; uint32_t compute_guild_card_checksum(const void* data, size_t size); template DestT convert_player_disp_data(const SrcT&) { static_assert(always_false::v, "unspecialized strcpy_t should never be called"); } template <> inline PlayerDispDataDCPCV3 convert_player_disp_data( const PlayerDispDataDCPCV3& src) { return src; } template <> inline PlayerDispDataDCPCV3 convert_player_disp_data( const PlayerDispDataBB& src) { return src.to_dcpcv3(); } template <> inline PlayerDispDataBB convert_player_disp_data( const PlayerDispDataDCPCV3& src) { return src.to_bb(); } template <> inline PlayerDispDataBB convert_player_disp_data( const PlayerDispDataBB& src) { return src; }