From f4e6a4009797c2e9a1d9c5bf5a328b01ba415c68 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 11 May 2024 22:31:16 -0700 Subject: [PATCH] clean up SaveFileFormats.hh --- src/SaveFileFormats.cc | 145 +++++---- src/SaveFileFormats.hh | 720 +++++++++++++++++++++-------------------- 2 files changed, 442 insertions(+), 423 deletions(-) diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 9dede319..b4d9302f 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -8,9 +8,74 @@ using namespace std; -// Originally there was going to be a language-based header, but then I decided -// against it. This string was already in use for that parser, so I didn't -// bother changing it. +struct DefaultSymbolChatEntry { + array language_to_name; + uint32_t spec; + array corner_objects; + array face_parts; + + SaveFileSymbolChatEntryBB to_entry(uint8_t language) const { + SaveFileSymbolChatEntryBB ret; + ret.present = 1; + ret.name.encode(this->language_to_name.at(language), language); + ret.spec.spec = this->spec; + for (size_t z = 0; z < 4; z++) { + ret.spec.corner_objects[z] = this->corner_objects[z]; + } + for (size_t z = 0; z < 12; z++) { + ret.spec.face_parts[z] = this->face_parts[z]; + } + return ret; + } +}; + +static const array DEFAULT_SYMBOL_CHATS = { + DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, + DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, + DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, + DefaultSymbolChatEntry{{"\tJ\xE3\x81\x86\xE3\x81\x87\xEF\xBD\x9E\xE3\x82\x93", "\tECrying", "\tEIch bin sauer!", "\tEJe suis triste", "\tELlanto", "\tB\xE5\x96\x82\xEF\xBD\x9E", "\tT\xE5\x96\x82\xEF\xBD\x9E", "\tK\xEC\x9D\x91~"}, 0x74, {0x074F, 0xFFFF, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x21, 0x20, 0x2E, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, + DefaultSymbolChatEntry{{"\tJ\xE3\x81\x8A\xE3\x81\x93\xE3\x81\xA3\xE3\x81\x9F\xEF\xBC\x81", "\tEI'm angry!", "\tEWeinen", "\tEJe suis en colere !", "\tEEnfado", "\tB\xE7\x94\x9F\xE6\xB0\x94\xE4\xBA\x86\xEF\xBC\x81", "\tT\xE7\x94\x9F\xE6\xB0\xA3\xE4\xBA\x86\xEF\xBC\x81", "\tK\xEB\x82\x98\xEC\x99\x94\xEB\x8B\xA4\xEF\xBC\x81"}, 0x5C, {0x0116, 0x0001, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x0B, 0x18, 0x1B, 0x01}, {0x0B, 0x28, 0x1B, 0x00}, {0x33, 0x20, 0x2A, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, + DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, +}; + +static const array DEFAULT_TECH_MENU_CONFIG = { + 0x0000, 0x0006, 0x0003, 0x0001, 0x0007, 0x0004, 0x0002, 0x0008, 0x0005, 0x0009, + 0x0012, 0x000F, 0x0010, 0x0011, 0x000D, 0x000A, 0x000B, 0x000C, 0x000E, 0x0000}; + +static const array DEFAULT_KEY_CONFIG = { + 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; + +static const array DEFAULT_JOYSTICK_CONFIG = { + 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; + +// Originally there was going to be a language-based header for .nsc files, but +// then I decided against it. This string was already in use for that parser, +// so I didn't bother changing it. const char* LegacySavedAccountDataBB::SIGNATURE = "newserv account file format; 7 sections present; sequential;"; ShuffleTables::ShuffleTables(PSOV2Encryption& crypt) { @@ -203,11 +268,11 @@ 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 = (now() - 946684800000000ULL) / 1000000; - for (size_t z = 0; z < PSOBBBaseSystemFile::DEFAULT_KEY_CONFIG.size(); z++) { - this->key_config[z] = PSOBBBaseSystemFile::DEFAULT_KEY_CONFIG[z]; + for (size_t z = 0; z < DEFAULT_KEY_CONFIG.size(); z++) { + this->key_config[z] = DEFAULT_KEY_CONFIG[z]; } - for (size_t z = 0; z < PSOBBBaseSystemFile::DEFAULT_JOYSTICK_CONFIG.size(); z++) { - this->joystick_config[z] = PSOBBBaseSystemFile::DEFAULT_JOYSTICK_CONFIG[z]; + for (size_t z = 0; z < DEFAULT_JOYSTICK_CONFIG.size(); z++) { + this->joystick_config[z] = DEFAULT_JOYSTICK_CONFIG[z]; } } @@ -400,11 +465,11 @@ shared_ptr PSOBBCharacterFile::create_from_config( ret->guild_card.language = ret->inventory.language; ret->guild_card.section_id = ret->disp.visual.section_id; ret->guild_card.char_class = ret->disp.visual.char_class; - for (size_t z = 0; z < PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS.size(); z++) { - ret->symbol_chats[z] = PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS[z].to_entry(language); + for (size_t z = 0; z < DEFAULT_SYMBOL_CHATS.size(); z++) { + ret->symbol_chats[z] = DEFAULT_SYMBOL_CHATS[z].to_entry(language); } - for (size_t z = 0; z < PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG.size(); z++) { - ret->tech_menu_shortcut_entries[z] = PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG[z]; + for (size_t z = 0; z < DEFAULT_TECH_MENU_CONFIG.size(); z++) { + ret->tech_menu_shortcut_entries[z] = DEFAULT_TECH_MENU_CONFIG[z]; } return ret; } @@ -623,20 +688,6 @@ PSOXBCharacterFileCharacter PSOBBCharacterFile::to_xb(uint64_t xb_user_id) const return ret; } -SaveFileSymbolChatEntryBB PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry(uint8_t language) const { - SaveFileSymbolChatEntryBB ret; - ret.present = 1; - ret.name.encode(this->language_to_name.at(language), language); - ret.spec.spec = this->spec; - for (size_t z = 0; z < 4; z++) { - ret.spec.corner_objects[z] = this->corner_objects[z]; - } - for (size_t z = 0; z < 12; z++) { - ret.spec.face_parts[z] = this->face_parts[z]; - } - return ret; -} - // TODO: Eliminate duplication between this function and the parallel function // in PlayerBankT void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLimits& limits) { @@ -821,50 +872,6 @@ void PSOBBCharacterFile::clear_all_material_usage() { } } -const array PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS = { - DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, - DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, - DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, - DefaultSymbolChatEntry{{"\tJ\xE3\x81\x86\xE3\x81\x87\xEF\xBD\x9E\xE3\x82\x93", "\tECrying", "\tEIch bin sauer!", "\tEJe suis triste", "\tELlanto", "\tB\xE5\x96\x82\xEF\xBD\x9E", "\tT\xE5\x96\x82\xEF\xBD\x9E", "\tK\xEC\x9D\x91~"}, 0x74, {0x074F, 0xFFFF, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x21, 0x20, 0x2E, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, - DefaultSymbolChatEntry{{"\tJ\xE3\x81\x8A\xE3\x81\x93\xE3\x81\xA3\xE3\x81\x9F\xEF\xBC\x81", "\tEI'm angry!", "\tEWeinen", "\tEJe suis en colere !", "\tEEnfado", "\tB\xE7\x94\x9F\xE6\xB0\x94\xE4\xBA\x86\xEF\xBC\x81", "\tT\xE7\x94\x9F\xE6\xB0\xA3\xE4\xBA\x86\xEF\xBC\x81", "\tK\xEB\x82\x98\xEC\x99\x94\xEB\x8B\xA4\xEF\xBC\x81"}, 0x5C, {0x0116, 0x0001, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x0B, 0x18, 0x1B, 0x01}, {0x0B, 0x28, 0x1B, 0x00}, {0x33, 0x20, 0x2A, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, - DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}}, -}; - -const array PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG = { - 0x0000, 0x0006, 0x0003, 0x0001, 0x0007, 0x0004, 0x0002, 0x0008, 0x0005, 0x0009, - 0x0012, 0x000F, 0x0010, 0x0011, 0x000D, 0x000A, 0x000B, 0x000C, 0x000E, 0x0000}; - -const array PSOBBBaseSystemFile::DEFAULT_KEY_CONFIG = { - 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; - -const array PSOBBBaseSystemFile::DEFAULT_JOYSTICK_CONFIG = { - 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, - 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; - static uint16_t crc16(const void* data, size_t size) { static const uint16_t table[0x100] = { // clang-format off diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 77db185e..019d3d74 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -17,16 +17,8 @@ #include "PlayerSubordinates.hh" #include "Text.hh" -struct ShuffleTables { - uint8_t forward_table[0x100]; - uint8_t reverse_table[0x100]; - - ShuffleTables(PSOV2Encryption& crypt); - - static uint32_t pseudorand(PSOV2Encryption& crypt, uint32_t prev); - - void shuffle(void* vdest, const void* vsrc, size_t size, bool reverse) const; -}; +//////////////////////////////////////////////////////////////////////////////// +// Memory card / VMU structures struct PSOVMSFileHeader { /* 0000 */ pstring short_desc; @@ -92,33 +84,19 @@ struct PSOGCIFileHeader { bool is_nte() const; } __packed_ws__(PSOGCIFileHeader, 0x2088); -struct PSOGCSystemFile { - /* 0000 */ be_uint32_t checksum; - /* 0004 */ be_int16_t music_volume; // 0 = full volume; -250 = min volume - /* 0006 */ int8_t sound_volume; // 0 = full volume; -100 = min volume - /* 0007 */ uint8_t language; - // This field stores the effective time zone offset between the server and - // client, in frames. The default value is 1728000, which corresponds to 16 - // hours. This is recomputed when the client receives a B1 command. - /* 0008 */ be_int32_t server_time_delta_frames; - /* 000C */ be_uint16_t udp_behavior; // 0 = auto, 1 = on, 2 = off - /* 000E */ be_uint16_t surround_sound_enabled; - /* 0010 */ parray event_flags; // Can be set by quest opcode D8 or E8 - /* 0110 */ parray unknown_a7; - // This timestamp is the number of seconds since 12:00AM on 1 January 2000. - // This field is also used as the round1 seed for encrypting the character and - // Guild Card files. - /* 0118 */ be_uint32_t creation_timestamp; - /* 011C */ -} __packed_ws__(PSOGCSystemFile, 0x11C); +//////////////////////////////////////////////////////////////////////////////// +// Subordinate structures -struct PSOGCEp3SystemFile { - /* 0000 */ PSOGCSystemFile base; - /* 011C */ int8_t unknown_a1; - /* 011D */ parray unknown_a2; - /* 0128 */ be_uint32_t unknown_a3; - /* 012C */ -} __packed_ws__(PSOGCEp3SystemFile, 0x12C); +struct ShuffleTables { + uint8_t forward_table[0x100]; + uint8_t reverse_table[0x100]; + + ShuffleTables(PSOV2Encryption& crypt); + + static uint32_t pseudorand(PSOV2Encryption& crypt, uint32_t prev); + + void shuffle(void* vdest, const void* vsrc, size_t size, bool reverse) const; +}; template struct SaveFileSymbolChatEntryT { @@ -218,6 +196,114 @@ check_struct_size(SaveFileShortcutEntryGC, 0x54); check_struct_size(SaveFileShortcutEntryXB, 0x54); check_struct_size(SaveFileShortcutEntryBB, 0xA4); +struct PSOBBTeamMembership { + /* 0000 */ le_uint32_t team_master_guild_card_number = 0; + /* 0004 */ le_uint32_t team_id = 0; + /* 0008 */ le_uint32_t unknown_a5 = 0; + /* 000C */ le_uint32_t unknown_a6 = 0; + /* 0010 */ uint8_t privilege_level = 0; + /* 0011 */ uint8_t unknown_a7 = 0; + /* 0012 */ uint8_t unknown_a8 = 0; + /* 0013 */ uint8_t unknown_a9 = 0; + /* 0014 */ pstring team_name; + /* 0034 */ parray flag_data; + /* 0834 */ le_uint32_t reward_flags = 0; + /* 0838 */ + + PSOBBTeamMembership() = default; +} __packed_ws__(PSOBBTeamMembership, 0x838); + +//////////////////////////////////////////////////////////////////////////////// +// System files + +struct PSOPCCreationTimeFile { // PSO______FLS + // The game creates this file if necessary and fills it with random data. + // Most of the random data appears to be a decoy; only one field is used. + // As in other PSO versions, creation_timestamp is used as an encryption key + // for the other save files, but only if the serial number isn't set in the + // Windows registry. + /* 0000 */ parray unused1; + /* 0624 */ le_uint32_t creation_timestamp; + /* 0628 */ parray unused2; + /* 1400 */ +} __packed_ws__(PSOPCCreationTimeFile, 0x1400); + +struct PSOPCSystemFile { // PSO______COM + /* 0000 */ le_uint32_t checksum; + // Most of these fields are guesses based on the format used in GC and the + // assumption that Sega didn't change much between versions. + /* 0004 */ le_int16_t music_volume; + /* 0006 */ int8_t sound_volume; + /* 0007 */ uint8_t language; + /* 0008 */ le_int32_t server_time_delta_frames; + /* 000C */ parray unknown_a4; // Last one is always 0x1234? + /* 002C */ parray event_flags; + /* 012C */ le_uint32_t round1_seed; + /* 0130 */ parray end_padding; + /* 0200 */ +} __packed_ws__(PSOPCSystemFile, 0x200); + +struct PSOGCSystemFile { + /* 0000 */ be_uint32_t checksum; + /* 0004 */ be_int16_t music_volume; // 0 = full volume; -250 = min volume + /* 0006 */ int8_t sound_volume; // 0 = full volume; -100 = min volume + /* 0007 */ uint8_t language; + // This field stores the effective time zone offset between the server and + // client, in frames. The default value is 1728000, which corresponds to 16 + // hours. This is recomputed when the client receives a B1 command. + /* 0008 */ be_int32_t server_time_delta_frames; + /* 000C */ be_uint16_t udp_behavior; // 0 = auto, 1 = on, 2 = off + /* 000E */ be_uint16_t surround_sound_enabled; + /* 0010 */ parray event_flags; // Can be set by quest opcode D8 or E8 + /* 0110 */ parray unknown_a7; + // This timestamp is the number of seconds since 12:00AM on 1 January 2000. + // This field is also used as the round1 seed for encrypting the character and + // Guild Card files. + /* 0118 */ be_uint32_t creation_timestamp; + /* 011C */ +} __packed_ws__(PSOGCSystemFile, 0x11C); + +struct PSOGCEp3SystemFile { + /* 0000 */ PSOGCSystemFile base; + /* 011C */ int8_t unknown_a1; + /* 011D */ parray unknown_a2; + /* 0128 */ be_uint32_t unknown_a3; + /* 012C */ +} __packed_ws__(PSOGCEp3SystemFile, 0x12C); + +struct PSOBBMinimalSystemFile { + /* 0000 */ be_uint32_t checksum = 0; + /* 0004 */ be_int16_t music_volume = 0; + /* 0006 */ int8_t sound_volume = 0; + /* 0007 */ uint8_t language = 0; + /* 0008 */ be_int32_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 */ +} __packed_ws__(PSOBBMinimalSystemFile, 0x114); + +struct PSOBBBaseSystemFile { + /* 0000 */ PSOBBMinimalSystemFile base; + /* 0114 */ parray key_config; + /* 0280 */ parray joystick_config; + /* 02B8 */ + + PSOBBBaseSystemFile(); +} __packed_ws__(PSOBBBaseSystemFile, 0x2B8); + +struct PSOBBFullSystemFile { + /* 0000 */ PSOBBBaseSystemFile base; + /* 02B8 */ PSOBBTeamMembership team_membership; + /* 0AF0 */ + + PSOBBFullSystemFile() = default; +} __packed_ws__(PSOBBFullSystemFile, 0xAF0); + +//////////////////////////////////////////////////////////////////////////////// +// Character files + struct PSODCV2CharacterFile { // See PSOGCCharacterFile::Character for descriptions of fields' meanings. /* 0000:---- */ PlayerInventory inventory; @@ -242,9 +328,10 @@ struct PSODCV2CharacterFile { /* 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.). + // it's represented with 16-bit integers in the various commands that send it + // to and from the server. 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; @@ -252,6 +339,47 @@ struct PSODCV2CharacterFile { /* 16F4:12D8 */ } __packed_ws__(PSODCV2CharacterFile, 0x16F4); +struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD + // See PSOGCCharacterFile::Character for descriptions of fields' meanings. + /* 00000 */ le_uint32_t signature; // 'CAEN' (stored as 4E 45 41 43) + /* 00004 */ le_uint32_t extra_headers; // 1 + /* 00008 */ le_uint32_t num_entries; // 0x80 + /* 0000C */ le_uint32_t entry_size; // 0x1D54 (actual entry size is +0x40) + /* 00010 */ parray unknown_a1; + struct CharacterEntry { + /* 0000 */ le_uint32_t present; // 1 if character present, 0 if empty + struct Character { + /* 0000 */ le_uint32_t checksum; + /* 0004 */ PlayerInventory inventory; + /* 0350 */ PlayerDispDataDCPCV3 disp; + /* 0420 */ be_uint32_t flags; + /* 0424 */ be_uint32_t creation_timestamp; + /* 0428 */ be_uint32_t signature; // == 0x6C5D889E? + /* 042C */ be_uint32_t play_time_seconds; + /* 0430 */ be_uint32_t option_flags; // TODO: document bits in this field + /* 0434 */ be_uint32_t save_count; + // TODO: Figure out what this is. On GC, this is where the bank data goes. + /* 0438 */ parray unknown_a2; + /* 0C0C */ GuildCardPC guild_card; + /* 0CFC */ parray symbol_chats; + // TODO: Figure out what this is. On GC, this is where chat shortcuts and + // challenge/battle records go. + /* 123C */ parray unknown_a3; + /* 1CDC */ parray tech_menu_shortcut_entries; + /* 1D04 */ parray unknown_a4; + /* 1D30 */ pstring serial_number; // As %08X (not decimal) + /* 1D40 */ pstring access_key; // As decimal + /* 1D50 */ le_uint32_t round2_seed; + /* 1D54 */ + } __packed_ws__(Character, 0x1D54); + /* 0004 */ Character character; + /* 1D58 */ parray unused; + /* 1D94 */ + } __packed_ws__(CharacterEntry, 0x1D94); + /* 00440 */ parray entries; + /* ECE40 */ +} __packed_ws__(PSOPCCharacterFile, 0xECE40); + struct PSOGCCharacterFile { /* 00000 */ be_uint32_t checksum; struct Character { @@ -393,44 +521,6 @@ struct PSOGCEp3CharacterFile { /* 194B0 */ } __packed_ws__(PSOGCEp3CharacterFile, 0x194B0); -struct PSOGCGuildCardFile { - /* 0000 */ be_uint32_t checksum; - /* 0004 */ parray unknown_a1; - struct GuildCardEntry { - /* 0000 */ GuildCardGCBE base; - /* 0090 */ uint8_t unknown_a1; - /* 0091 */ uint8_t unknown_a2; - /* 0092 */ uint8_t unknown_a3; - /* 0093 */ uint8_t unknown_a4; - /* 0094 */ pstring comment; - /* 0100 */ - } __packed_ws__(GuildCardEntry, 0x100); - /* 00C4 */ parray entries; - /* D2C4 */ parray blocked_senders; - /* E284 */ be_uint32_t creation_timestamp; - /* E288 */ be_uint32_t round2_seed; - /* E28C */ -} __packed_ws__(PSOGCGuildCardFile, 0xE28C); - -struct PSOGCSnapshotFile { - /* 00000 */ be_uint32_t checksum; - /* 00004 */ be_uint16_t width; - /* 00006 */ be_uint16_t height; - // Pixels are stored as 4x4 blocks of RGB565 values. See the implementation - // of decode_image for details. - /* 00008 */ parray pixels; - /* 18008 */ uint8_t unknown_a1; // Always 0x18? - /* 18009 */ uint8_t unknown_a2; - /* 1800A */ be_int16_t max_players; - /* 1800C */ parray players_present; - /* 1803C */ parray player_levels; - /* 1806C */ parray, 12> player_names; - /* 1818C */ - - bool checksum_correct() const; - Image decode_image() const; -} __packed_ws__(PSOGCSnapshotFile, 0x1818C); - struct PSOXBCharacterFileCharacter { // This structure is internally split into two by the game. The offsets here // are relative to the start of this structure (first column), and relative @@ -474,6 +564,203 @@ struct PSOXBCharacterFileCharacter { /* 28C8:24AC */ } __packed_ws__(PSOXBCharacterFileCharacter, 0x28C8); +struct PSOBBCharacterFile { + // Most fields have the same meanings as in PSOGCCharacterFile::Character. + // This is the character data used by the server for all game versions, and + // is also the format used in .psochar files. + + /* 0000 */ PlayerInventory inventory; + /* 034C */ PlayerDispDataBB disp; + /* 04DC */ le_uint32_t flags = 0; + /* 04E0 */ le_uint32_t creation_timestamp = 0; + /* 04E4 */ le_uint32_t signature = 0xC87ED5B1; + /* 04E8 */ le_uint32_t play_time_seconds = 0; + /* 04EC */ le_uint32_t option_flags = 0x00040058; + /* 04F0 */ le_uint32_t save_count = 0; + /* 04F4 */ QuestFlags quest_flags; + /* 06F4 */ le_uint32_t death_count = 0; + /* 06F8 */ PlayerBank200 bank; + /* 19C0 */ GuildCardBB guild_card; + /* 1AC8 */ le_uint32_t unknown_a3 = 0; + /* 1ACC */ parray symbol_chats; + /* 1FAC */ parray shortcuts; + /* 29EC */ pstring auto_reply; + /* 2B44 */ pstring info_board; + /* 2C9C */ PlayerRecordsBattle battle_records; + /* 2CB4 */ parray unknown_a4; + /* 2CB8 */ PlayerRecordsChallengeBB challenge_records; + /* 2DF8 */ parray tech_menu_shortcut_entries; + /* 2E20 */ ChoiceSearchConfig choice_search_config; + /* 2E38 */ parray unknown_a6; + /* 2E48 */ parray quest_counters; + /* 2E88 */ PlayerRecordsBattle offline_battle_records; + /* 2EA0 */ parray unknown_a7; + /* 2EA4 */ + + PSOBBCharacterFile() = default; + + PlayerDispDataBBPreview to_preview() const; + + static std::shared_ptr create_from_config( + uint32_t guild_card_number, + uint8_t language, + const PlayerVisualConfig& visual, + const std::string& name, + std::shared_ptr level_table); + static std::shared_ptr create_from_preview( + uint32_t guild_card_number, + uint8_t language, + const PlayerDispDataBBPreview& preview, + std::shared_ptr level_table); + 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); + void add_meseta(uint32_t amount); + void remove_meseta(uint32_t amount, bool allow_overdraft); + + uint8_t get_technique_level(uint8_t which) const; // Returns FF or 00-1D + void set_technique_level(uint8_t which, uint8_t level); + + enum class MaterialType : int8_t { + HP = -2, + TP = -1, + POWER = 0, + MIND = 1, + EVADE = 2, + DEF = 3, + LUCK = 4, + }; + + uint8_t get_material_usage(MaterialType which) const; + void set_material_usage(MaterialType which, uint8_t usage); + void clear_all_material_usage(); + + PSOGCCharacterFile::Character to_gc() const; + PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) const; +} __packed_ws__(PSOBBCharacterFile, 0x2EA4); + +//////////////////////////////////////////////////////////////////////////////// +// Guild Card files + +struct PSOPCGuildCardFile { // PSO______GUD + /* 0000 */ le_uint32_t checksum; + // TODO: Figure out the PC guild card format. + /* 0004 */ parray unknown_a1; + /* 7984 */ le_uint32_t creation_timestamp; + /* 7988 */ le_uint32_t round2_seed; + /* 798C */ parray end_padding; + /* 7A00 */ +} __packed_ws__(PSOPCGuildCardFile, 0x7A00); + +struct PSOGCGuildCardFile { + /* 0000 */ be_uint32_t checksum; + /* 0004 */ parray unknown_a1; + struct GuildCardEntry { + /* 0000 */ GuildCardGCBE base; + /* 0090 */ uint8_t unknown_a1; + /* 0091 */ uint8_t unknown_a2; + /* 0092 */ uint8_t unknown_a3; + /* 0093 */ uint8_t unknown_a4; + /* 0094 */ pstring comment; + /* 0100 */ + } __packed_ws__(GuildCardEntry, 0x100); + /* 00C4 */ parray entries; + /* D2C4 */ parray blocked_senders; + /* E284 */ be_uint32_t creation_timestamp; + /* E288 */ be_uint32_t round2_seed; + /* E28C */ +} __packed_ws__(PSOGCGuildCardFile, 0xE28C); + +struct PSOBBGuildCardFile { + struct Entry { + /* 0000 */ GuildCardBB data; + /* 0108 */ pstring comment; + /* 01B8 */ parray unknown_a1; + /* 01BC */ + + void clear(); + } __packed_ws__(Entry, 0x1BC); + + /* 0000 */ PSOBBMinimalSystemFile system_file; + /* 0114 */ parray blocked; + /* 1DF4 */ parray unknown_a2; + /* 1F74 */ parray entries; + /* D590 */ + + PSOBBGuildCardFile() = default; + + uint32_t checksum() const; +} __packed_ws__(PSOBBGuildCardFile, 0xD590); + +//////////////////////////////////////////////////////////////////////////////// +// Snapshot files + +struct PSOGCSnapshotFile { + /* 00000 */ be_uint32_t checksum; + /* 00004 */ be_uint16_t width; + /* 00006 */ be_uint16_t height; + // Pixels are stored as 4x4 blocks of RGB565 values. See the implementation + // of decode_image for details. + /* 00008 */ parray pixels; + /* 18008 */ uint8_t unknown_a1; // Always 0x18? + /* 18009 */ uint8_t unknown_a2; + /* 1800A */ be_int16_t max_players; + /* 1800C */ parray players_present; + /* 1803C */ parray player_levels; + /* 1806C */ parray, 12> player_names; + /* 1818C */ + + bool checksum_correct() const; + Image decode_image() const; +} __packed_ws__(PSOGCSnapshotFile, 0x1818C); + +//////////////////////////////////////////////////////////////////////////////// +// Obsolete newserv-specific formats (for backward compatibility only) + +struct LegacySavedPlayerDataBB { // .nsc file format + static constexpr uint64_t SIGNATURE_V0 = 0x6E65777365727620; + static constexpr uint64_t SIGNATURE_V1 = 0xA904332D5CEF0296; + + /* 0000 */ be_uint64_t signature = SIGNATURE_V1; + /* 0008 */ parray unused; + /* 0028 */ PlayerRecordsBattle battle_records; + /* 0040 */ PlayerDispDataBBPreview preview; + /* 00BC */ pstring auto_reply; + /* 0214 */ PlayerBank200 bank; + /* 14DC */ PlayerRecordsChallengeBB challenge_records; + /* 161C */ PlayerDispDataBB disp; + /* 17AC */ pstring guild_card_description; + /* 185C */ pstring info_board; + /* 19B4 */ PlayerInventory inventory; + /* 1D00 */ parray unknown_a2; + /* 1D04 */ QuestFlags quest_flags; + /* 1F04 */ le_uint32_t death_count; + /* 1F08 */ parray quest_counters; + /* 1F60 */ parray tech_menu_shortcut_entries; + /* 1F88 */ +} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88); + +struct LegacySavedAccountDataBB { // .nsa file format + static const char* SIGNATURE; + + /* 0000 */ pstring signature; + /* 0040 */ parray blocked_senders; + /* 00B8 */ PSOBBGuildCardFile guild_card_file; + /* D648 */ PSOBBFullSystemFile system_file; + /* E138 */ le_uint32_t unused; + /* E13C */ le_uint32_t option_flags; + /* E140 */ parray shortcuts; + /* EB80 */ parray symbol_chats; + /* F060 */ pstring team_name; + /* F080 */ +} __packed_ws__(LegacySavedAccountDataBB, 0xF080); + +//////////////////////////////////////////////////////////////////////////////// +// Encoding/decoding functions + template std::string decrypt_data_section(const void* data_section, size_t size, uint32_t round1_seed, size_t max_decrypt_bytes = 0) { if (max_decrypt_bytes == 0) { @@ -646,280 +933,5 @@ uint32_t compute_psogc_timestamp( uint8_t minute, uint8_t second); -struct PSOPCCreationTimeFile { // PSO______FLS - // The game creates this file if necessary and fills it with random data. - // Most of the random data appears to be a decoy; only one field is used. - // As in other PSO versions, creation_timestamp is used as an encryption key - // for the other save files, but only if the serial number isn't set in the - // Windows registry. - /* 0000 */ parray unused1; - /* 0624 */ le_uint32_t creation_timestamp; - /* 0628 */ parray unused2; - /* 1400 */ -} __packed_ws__(PSOPCCreationTimeFile, 0x1400); - -struct PSOPCSystemFile { // PSO______COM - /* 0000 */ le_uint32_t checksum; - // Most of these fields are guesses based on the format used in GC and the - // assumption that Sega didn't change much between versions. - /* 0004 */ le_int16_t music_volume; - /* 0006 */ int8_t sound_volume; - /* 0007 */ uint8_t language; - /* 0008 */ le_int32_t server_time_delta_frames; - /* 000C */ parray unknown_a4; // Last one is always 0x1234? - /* 002C */ parray event_flags; - /* 012C */ le_uint32_t round1_seed; - /* 0130 */ parray end_padding; - /* 0200 */ -} __packed_ws__(PSOPCSystemFile, 0x200); - -struct PSOPCGuildCardFile { // PSO______GUD - /* 0000 */ le_uint32_t checksum; - // TODO: Figure out the PC guild card format. - /* 0004 */ parray unknown_a1; - /* 7984 */ le_uint32_t creation_timestamp; - /* 7988 */ le_uint32_t round2_seed; - /* 798C */ parray end_padding; - /* 7A00 */ -} __packed_ws__(PSOPCGuildCardFile, 0x7A00); - -struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD - /* 00000 */ le_uint32_t signature; // 'CAEN' (stored as 4E 45 41 43) - /* 00004 */ le_uint32_t extra_headers; // 1 - /* 00008 */ le_uint32_t num_entries; // 0x80 - /* 0000C */ le_uint32_t entry_size; // 0x1D54 (actual entry size is +0x40) - /* 00010 */ parray unknown_a1; - struct CharacterEntry { - /* 0000 */ le_uint32_t present; // 1 if character present, 0 if empty - struct Character { - /* 0000 */ le_uint32_t checksum; - /* 0004 */ PlayerInventory inventory; - /* 0350 */ PlayerDispDataDCPCV3 disp; - /* 0420 */ be_uint32_t flags; - /* 0424 */ be_uint32_t creation_timestamp; - /* 0428 */ be_uint32_t signature; // == 0x6C5D889E? - /* 042C */ be_uint32_t play_time_seconds; - /* 0430 */ be_uint32_t option_flags; // TODO: document bits in this field - /* 0434 */ be_uint32_t save_count; - // TODO: Figure out what this is. On GC, this is where the bank data goes. - /* 0438 */ parray unknown_a2; - /* 0C0C */ GuildCardPC guild_card; - /* 0CFC */ parray symbol_chats; - // TODO: Figure out what this is. On GC, this is where chat shortcuts and - // challenge/battle records go. - /* 123C */ parray unknown_a3; - /* 1CDC */ parray tech_menu_shortcut_entries; - /* 1D04 */ parray unknown_a4; - /* 1D30 */ pstring serial_number; // As %08X (not decimal) - /* 1D40 */ pstring access_key; // As decimal - /* 1D50 */ le_uint32_t round2_seed; - /* 1D54 */ - } __packed_ws__(Character, 0x1D54); - /* 0004 */ Character character; - /* 1D58 */ parray unused; - /* 1D94 */ - } __packed_ws__(CharacterEntry, 0x1D94); - /* 00440 */ parray entries; - /* ECE40 */ -} __packed_ws__(PSOPCCharacterFile, 0xECE40); - -struct PSOBBMinimalSystemFile { - /* 0000 */ be_uint32_t checksum = 0; - /* 0004 */ be_int16_t music_volume = 0; - /* 0006 */ int8_t sound_volume = 0; - /* 0007 */ uint8_t language = 0; - /* 0008 */ be_int32_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 */ -} __packed_ws__(PSOBBMinimalSystemFile, 0x114); - -struct PSOBBTeamMembership { - /* 0000 */ le_uint32_t team_master_guild_card_number = 0; - /* 0004 */ le_uint32_t team_id = 0; - /* 0008 */ le_uint32_t unknown_a5 = 0; - /* 000C */ le_uint32_t unknown_a6 = 0; - /* 0010 */ uint8_t privilege_level = 0; - /* 0011 */ uint8_t unknown_a7 = 0; - /* 0012 */ uint8_t unknown_a8 = 0; - /* 0013 */ uint8_t unknown_a9 = 0; - /* 0014 */ pstring team_name; - /* 0034 */ parray flag_data; - /* 0834 */ le_uint32_t reward_flags = 0; - /* 0838 */ - - PSOBBTeamMembership() = default; -} __packed_ws__(PSOBBTeamMembership, 0x838); - -struct PSOBBBaseSystemFile { - /* 0000 */ PSOBBMinimalSystemFile base; - /* 0114 */ parray key_config; - /* 0280 */ parray joystick_config; - /* 02B8 */ - - static const std::array DEFAULT_KEY_CONFIG; - static const std::array DEFAULT_JOYSTICK_CONFIG; - - PSOBBBaseSystemFile(); -} __packed_ws__(PSOBBBaseSystemFile, 0x2B8); - -struct PSOBBFullSystemFile { - /* 0000 */ PSOBBBaseSystemFile base; - /* 02B8 */ PSOBBTeamMembership team_membership; - /* 0AF0 */ - - PSOBBFullSystemFile() = default; -} __packed_ws__(PSOBBFullSystemFile, 0xAF0); - -struct PSOBBCharacterFile { - struct DefaultSymbolChatEntry { - std::array language_to_name; - uint32_t spec; - std::array corner_objects; - std::array face_parts; - - SaveFileSymbolChatEntryBB to_entry(uint8_t language) const; - }; - - /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataBB disp; - /* 04DC */ le_uint32_t flags = 0; - /* 04E0 */ le_uint32_t creation_timestamp = 0; - /* 04E4 */ le_uint32_t signature = 0xC87ED5B1; - /* 04E8 */ le_uint32_t play_time_seconds = 0; - /* 04EC */ le_uint32_t option_flags = 0x00040058; - /* 04F0 */ le_uint32_t save_count = 0; - /* 04F4 */ QuestFlags quest_flags; - /* 06F4 */ le_uint32_t death_count = 0; - /* 06F8 */ PlayerBank200 bank; - /* 19C0 */ GuildCardBB guild_card; - /* 1AC8 */ le_uint32_t unknown_a3 = 0; - /* 1ACC */ parray symbol_chats; - /* 1FAC */ parray shortcuts; - /* 29EC */ pstring auto_reply; - /* 2B44 */ pstring info_board; - /* 2C9C */ PlayerRecordsBattle battle_records; - /* 2CB4 */ parray unknown_a4; - /* 2CB8 */ PlayerRecordsChallengeBB challenge_records; - /* 2DF8 */ parray tech_menu_shortcut_entries; - /* 2E20 */ ChoiceSearchConfig choice_search_config; - /* 2E38 */ parray unknown_a6; - /* 2E48 */ parray quest_counters; - /* 2E88 */ PlayerRecordsBattle offline_battle_records; - /* 2EA0 */ parray unknown_a7; - /* 2EA4 */ - - static const std::array DEFAULT_SYMBOL_CHATS; - static const std::array DEFAULT_TECH_MENU_CONFIG; - - PSOBBCharacterFile() = default; - - PlayerDispDataBBPreview to_preview() const; - - static std::shared_ptr create_from_config( - uint32_t guild_card_number, - uint8_t language, - const PlayerVisualConfig& visual, - const std::string& name, - std::shared_ptr level_table); - static std::shared_ptr create_from_preview( - uint32_t guild_card_number, - uint8_t language, - const PlayerDispDataBBPreview& preview, - std::shared_ptr level_table); - 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); - void add_meseta(uint32_t amount); - void remove_meseta(uint32_t amount, bool allow_overdraft); - - uint8_t get_technique_level(uint8_t which) const; // Returns FF or 00-1D - void set_technique_level(uint8_t which, uint8_t level); - - enum class MaterialType : int8_t { - HP = -2, - TP = -1, - POWER = 0, - MIND = 1, - EVADE = 2, - DEF = 3, - LUCK = 4, - }; - - uint8_t get_material_usage(MaterialType which) const; - void set_material_usage(MaterialType which, uint8_t usage); - void clear_all_material_usage(); - - PSOGCCharacterFile::Character to_gc() const; - PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) const; -} __packed_ws__(PSOBBCharacterFile, 0x2EA4); - -struct PSOBBGuildCardFile { - struct Entry { - /* 0000 */ GuildCardBB data; - /* 0108 */ pstring comment; - /* 01B8 */ parray unknown_a1; - /* 01BC */ - - void clear(); - } __packed_ws__(Entry, 0x1BC); - - /* 0000 */ PSOBBMinimalSystemFile system_file; - /* 0114 */ parray blocked; - /* 1DF4 */ parray unknown_a2; - /* 1F74 */ parray entries; - /* D590 */ - - PSOBBGuildCardFile() = default; - - uint32_t checksum() const; -} __packed_ws__(PSOBBGuildCardFile, 0xD590); - -// This format is specific to newserv and is no longer used, but remains here -// for backward compatibility. -struct LegacySavedPlayerDataBB { // .nsc file format - static constexpr uint64_t SIGNATURE_V0 = 0x6E65777365727620; - static constexpr uint64_t SIGNATURE_V1 = 0xA904332D5CEF0296; - - /* 0000 */ be_uint64_t signature = SIGNATURE_V1; - /* 0008 */ parray unused; - /* 0028 */ PlayerRecordsBattle battle_records; - /* 0040 */ PlayerDispDataBBPreview preview; - /* 00BC */ pstring auto_reply; - /* 0214 */ PlayerBank200 bank; - /* 14DC */ PlayerRecordsChallengeBB challenge_records; - /* 161C */ PlayerDispDataBB disp; - /* 17AC */ pstring guild_card_description; - /* 185C */ pstring info_board; - /* 19B4 */ PlayerInventory inventory; - /* 1D00 */ parray unknown_a2; - /* 1D04 */ QuestFlags quest_flags; - /* 1F04 */ le_uint32_t death_count; - /* 1F08 */ parray quest_counters; - /* 1F60 */ parray tech_menu_shortcut_entries; - /* 1F88 */ -} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88); - -// This format is specific to newserv and is no longer used, but remains here -// for backward compatibility. -struct LegacySavedAccountDataBB { // .nsa file format - static const char* SIGNATURE; - - /* 0000 */ pstring signature; - /* 0040 */ parray blocked_senders; - /* 00B8 */ PSOBBGuildCardFile guild_card_file; - /* D648 */ PSOBBFullSystemFile system_file; - /* E138 */ le_uint32_t unused; - /* E13C */ le_uint32_t option_flags; - /* E140 */ parray shortcuts; - /* EB80 */ parray symbol_chats; - /* F060 */ pstring team_name; - /* F080 */ -} __packed_ws__(LegacySavedAccountDataBB, 0xF080); - -std::string encode_psobb_hangame_credentials(const std::string& user_id, const std::string& token, const std::string& unused = ""); +std::string encode_psobb_hangame_credentials( + const std::string& user_id, const std::string& token, const std::string& unused = "");