diff --git a/notes/AITalk.bin-format.txt b/notes/AITalk.bin-format.txt index fa4168ec..46ebb6c5 100644 --- a/notes/AITalk.bin-format.txt +++ b/notes/AITalk.bin-format.txt @@ -11,6 +11,6 @@ struct AITalkBin { be_uint32_t percent_chance; // 0-100 be_uint32_t count; be_uint32_t string_ids[count]; - } __attribute__((packed)); - } __attribute__((packed)); -} __attribute__((packed)); + } __packed__; + } __packed__; +} __packed__; diff --git a/src/AFSArchive.cc b/src/AFSArchive.cc index 4fd04a29..c3d791c4 100644 --- a/src/AFSArchive.cc +++ b/src/AFSArchive.cc @@ -7,6 +7,8 @@ #include #include +#include "Text.hh" + using namespace std; AFSArchive::AFSArchive(shared_ptr data) @@ -14,12 +16,12 @@ AFSArchive::AFSArchive(shared_ptr data) struct FileHeader { be_uint32_t magic; le_uint32_t num_files; - } __attribute__((packed)); + } __packed_ws__(FileHeader, 8); struct FileEntry { le_uint32_t offset; le_uint32_t size; - } __attribute__((packed)); + } __packed_ws__(FileEntry, 8); StringReader r(*this->data); const auto& header = r.get(); diff --git a/src/BMLArchive.cc b/src/BMLArchive.cc index 06e3a915..4f8ee4a4 100644 --- a/src/BMLArchive.cc +++ b/src/BMLArchive.cc @@ -9,16 +9,21 @@ using namespace std; template -struct BMLHeader { +struct BMLHeaderT { using U32T = typename std::conditional::type; parray unknown_a1; U32T num_entries; parray unknown_a2; -} __attribute__((packed)); +} __packed__; + +using BMLHeader = BMLHeaderT; +using BMLHeaderBE = BMLHeaderT; +check_struct_size(BMLHeader, 0x40); +check_struct_size(BMLHeaderBE, 0x40); template -struct BMLHeaderEntry { +struct BMLHeaderEntryT { using U32T = typename std::conditional::type; pstring filename; @@ -28,17 +33,22 @@ struct BMLHeaderEntry { U32T compressed_gvm_size; U32T decompressed_gvm_size; parray unknown_a2; -} __attribute__((packed)); +} __packed__; + +using BMLHeaderEntry = BMLHeaderEntryT; +using BMLHeaderEntryBE = BMLHeaderEntryT; +check_struct_size(BMLHeaderEntry, 0x40); +check_struct_size(BMLHeaderEntryBE, 0x40); template void BMLArchive::load_t() { StringReader r(*this->data); - const auto& header = r.get>(); + const auto& header = r.get>(); size_t offset = 0x800; while (this->entries.size() < header.num_entries) { - const auto& entry = r.get>(); + const auto& entry = r.get>(); if (offset + entry.compressed_size > this->data->size()) { throw runtime_error("BML data entry extends beyond end of data"); diff --git a/src/BattleParamsIndex.hh b/src/BattleParamsIndex.hh index cc2c26af..dcd40028 100644 --- a/src/BattleParamsIndex.hh +++ b/src/BattleParamsIndex.hh @@ -36,7 +36,7 @@ public: /* 28 */ le_uint32_t unknown_a15; /* 2C */ le_uint32_t unknown_a16; /* 30 */ - } __attribute__((packed)); + } __packed_ws__(AttackData, 0x30); struct ResistData { /* 00 */ le_int16_t evp_bonus; @@ -51,7 +51,7 @@ public: /* 18 */ le_uint32_t unknown_a9; /* 1C */ le_int32_t dfp_bonus; /* 20 */ - } __attribute__((packed)); + } __packed_ws__(ResistData, 0x20); struct MovementData { /* 00 */ le_float idle_move_speed; @@ -67,7 +67,7 @@ public: /* 28 */ le_uint32_t unknown_a7; /* 2C */ le_uint32_t unknown_a8; /* 30 */ - } __attribute__((packed)); + } __packed_ws__(MovementData, 0x30); struct Table { /* 0000 */ parray, 4> stats; @@ -77,7 +77,7 @@ public: /* F600 */ void print(FILE* stream) const; - } __attribute__((packed)); + } __packed_ws__(Table, 0xF600); BattleParamsIndex( std::shared_ptr data_on_ep1, // BattleParamEntry_on.dat diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index cbfca566..9a60fe82 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1284,8 +1284,8 @@ static void server_command_edit(shared_ptr c, const std::string& args) { p->disp.stats.experience = stoul(tokens.at(1)); } else if (tokens.at(0) == "level" && cheats_allowed) { uint32_t level = stoul(tokens.at(1)) - 1; - p->disp.stats.reset_to_base(p->disp.visual.char_class, s->level_table); - p->disp.stats.advance_to_level(p->disp.visual.char_class, level, s->level_table); + s->level_table->reset_to_base(p->disp.stats, p->disp.visual.char_class); + s->level_table->advance_to_level(p->disp.stats, level, p->disp.visual.char_class); } else if (tokens.at(0) == "namecolor") { uint32_t new_color; sscanf(tokens.at(1).c_str(), "%8X", &new_color); @@ -1434,9 +1434,10 @@ static void server_command_bbchar_savechar(shared_ptr c, const std::stri } c->pending_character_export = std::move(pending_export); - // Request the player data. The client will respond with a 61, and the handler - // for that command will execute the conversion - send_get_player_info(c); + + // Request the player data. The client will respond with a 61 or 30, and the + // handler for either of those commands will execute the conversion + send_get_player_info(c, true); } static void server_command_bbchar(shared_ptr c, const std::string& args) { diff --git a/src/ChoiceSearch.hh b/src/ChoiceSearch.hh index 5c32be4f..fed105de 100644 --- a/src/ChoiceSearch.hh +++ b/src/ChoiceSearch.hh @@ -10,12 +10,16 @@ class Client; -struct ChoiceSearchConfig { - le_uint32_t disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3 +template +struct ChoiceSearchConfigT { + using U16T = typename std::conditional::type; + using U32T = typename std::conditional::type; + + U32T disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3 struct Entry { - le_uint16_t parent_choice_id = 0; - le_uint16_t choice_id = 0; - } __attribute__((packed)); + U16T parent_choice_id = 0; + U16T choice_id = 0; + } __packed_ws__(Entry, 4); parray entries; int32_t get_setting(uint16_t parent_choice_id) const { @@ -26,7 +30,24 @@ struct ChoiceSearchConfig { } return -1; } -} __attribute__((packed)); + + operator ChoiceSearchConfigT() const { + ChoiceSearchConfigT ret; + ret.disabled = this->disabled.load(); + for (size_t z = 0; z < this->entries.size(); z++) { + auto& ret_e = ret.entries[z]; + const auto& this_e = this->entries[z]; + ret_e.parent_choice_id = this_e.parent_choice_id.load(); + ret_e.choice_id = this_e.choice_id.load(); + } + return ret; + } +} __packed__; + +using ChoiceSearchConfig = ChoiceSearchConfigT; +using ChoiceSearchConfigBE = ChoiceSearchConfigT; +check_struct_size(ChoiceSearchConfig, 0x18); +check_struct_size(ChoiceSearchConfigBE, 0x18); struct ChoiceSearchCategory { struct Choice { diff --git a/src/Client.cc b/src/Client.cc index 927458cf..9a2cce95 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -486,8 +486,8 @@ void Client::create_battle_overlay(shared_ptr rules, shared_p uint8_t char_class = this->overlay_character_data->disp.visual.char_class; auto& stats = this->overlay_character_data->disp.stats; - stats.reset_to_base(char_class, level_table); - stats.advance_to_level(char_class, target_level, level_table); + level_table->reset_to_base(stats, char_class); + level_table->advance_to_level(stats, target_level, char_class); stats.esp = 40; stats.meseta = 300; @@ -532,8 +532,8 @@ void Client::create_challenge_overlay(Version version, size_t template_index, sh overlay->inventory.items[13].extension_data2 = 1; - overlay->disp.stats.reset_to_base(overlay->disp.visual.char_class, level_table); - overlay->disp.stats.advance_to_level(overlay->disp.visual.char_class, tpl.level, level_table); + level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.char_class); + level_table->advance_to_level(overlay->disp.stats, tpl.level, overlay->disp.visual.char_class); overlay->disp.stats.esp = 40; overlay->disp.stats.unknown_a3 = 10.0; @@ -753,6 +753,7 @@ void Client::load_all_files() { if (header.flag != 0x00000000) { throw runtime_error("incorrect flag in character file header"); } + static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBFullSystemFile) == 0x3994, ".psochar size is incorrect"); this->character_data = make_shared(freadx(f.get())); files_manager->set_character(char_filename, this->character_data); player_data_log.info("Loaded character data from %s", char_filename.c_str()); @@ -831,7 +832,6 @@ void Client::load_all_files() { this->character_data->inventory = nsc_data.inventory; this->character_data->disp = nsc_data.disp; this->character_data->play_time_seconds = 0; - this->character_data->unknown_a2 = nsc_data.unknown_a2; this->character_data->quest_flags = nsc_data.quest_flags; this->character_data->death_count = nsc_data.death_count; this->character_data->bank = nsc_data.bank; @@ -846,7 +846,7 @@ void Client::load_all_files() { this->character_data->info_board = nsc_data.info_board; this->character_data->battle_records = nsc_data.battle_records; this->character_data->challenge_records = nsc_data.challenge_records; - this->character_data->tech_menu_config = nsc_data.tech_menu_config; + this->character_data->tech_menu_shortcut_entries = nsc_data.tech_menu_shortcut_entries; this->character_data->quest_counters = nsc_data.quest_counters; if (nsa_data) { this->character_data->option_flags = nsa_data->option_flags; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 305c8cc8..4653b16e 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -14,8 +14,6 @@ #include "SaveFileFormats.hh" #include "Text.hh" -#define __packed__ __attribute__((packed)) - // This file is newserv's canonical reference of the PSO client/server protocol. // For the unfamiliar, the le_uint and be_uint types (from phosg/Encoding.hh) @@ -154,7 +152,7 @@ struct S_ServerInit_Patch_02 { le_uint32_t client_key = 0; // Key for commands sent by client // The client rejects the command if it's larger than this size, so we can't // add the after_message like we do in the other server init commands. -} __packed__; +} __packed_ws__(S_ServerInit_Patch_02, 0x48); // 02 (C->S): Encryption started // No arguments @@ -174,7 +172,7 @@ struct C_Login_Patch_04 { pstring username; pstring password; pstring email; -} __packed__; +} __packed_ws__(C_Login_Patch_04, 0x6C); // 05 (S->C): Disconnect // No arguments @@ -187,7 +185,7 @@ struct S_OpenFile_Patch_06 { le_uint32_t unknown_a1 = 0; le_uint32_t size = 0; pstring filename; -} __packed__; +} __packed_ws__(S_OpenFile_Patch_06, 0x38); // 07 (S->C): Write file // The client's handler table says this command's maximum size is 0x6010 @@ -202,7 +200,7 @@ struct S_WriteFileHeader_Patch_07 { le_uint32_t chunk_checksum = 0; // CRC32 of the following chunk data le_uint32_t chunk_size = 0; // The chunk data immediately follows here -} __packed__; +} __packed_ws__(S_WriteFileHeader_Patch_07, 0x0C); // 08 (S->C): Close current file // The unused field is optional. It's not clear whether this field was ever @@ -211,13 +209,13 @@ struct S_WriteFileHeader_Patch_07 { struct S_CloseCurrentFile_Patch_08 { le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(S_CloseCurrentFile_Patch_08, 4); // 09 (S->C): Enter directory struct S_EnterDirectory_Patch_09 { pstring name; -} __packed__; +} __packed_ws__(S_EnterDirectory_Patch_09, 0x40); // 0A (S->C): Exit directory // No arguments @@ -230,7 +228,7 @@ struct S_EnterDirectory_Patch_09 { struct S_FileChecksumRequest_Patch_0C { le_uint32_t request_id = 0; pstring filename; -} __packed__; +} __packed_ws__(S_FileChecksumRequest_Patch_0C, 0x24); // 0D (S->C): End of file checksum requests // No arguments @@ -243,7 +241,7 @@ struct C_FileInformation_Patch_0F { le_uint32_t request_id = 0; // Matches request_id from an earlier 0C command le_uint32_t checksum = 0; // CRC32 of the file's data le_uint32_t size = 0; -} __packed__; +} __packed_ws__(C_FileInformation_Patch_0F, 0x0C); // 10 (C->S): End of file information command list // No arguments @@ -253,7 +251,7 @@ struct C_FileInformation_Patch_0F { struct S_StartFileDownloads_Patch_11 { le_uint32_t total_bytes = 0; le_uint32_t num_files = 0; -} __packed__; +} __packed_ws__(S_StartFileDownloads_Patch_11, 0x08); // 12 (S->C): End patch session successfully // No arguments @@ -272,14 +270,13 @@ struct S_StartFileDownloads_Patch_11 { // except the port field is big-endian for some reason. template -struct S_Reconnect { +struct S_ReconnectT { be_uint32_t address = 0; PortT port = 0; le_uint16_t unused = 0; } __packed__; - -struct S_Reconnect_Patch_14 : S_Reconnect { -} __packed__; +using S_Reconnect_Patch_14 = S_ReconnectT; +check_struct_size(S_Reconnect_Patch_14, 0x08); // 15 (S->C): Login failure // No arguments @@ -311,7 +308,7 @@ struct SC_TextHeader_01_06_11_B0_EE { le_uint32_t unused = 0; le_uint32_t guild_card_number = 0; // Text immediately follows here -} __packed__; +} __packed_ws__(SC_TextHeader_01_06_11_B0_EE, 8); // 02 (S->C): Start encryption (except on BB) // Internal name: RcvPsoConnectV2 @@ -334,10 +331,10 @@ struct S_ServerInitDefault_DC_PC_V3_02_17_91_9B { pstring copyright; le_uint32_t server_key = 0; // Key for data sent by server le_uint32_t client_key = 0; // Key for data sent by client -} __packed__; +} __packed_ws__(S_ServerInitDefault_DC_PC_V3_02_17_91_9B, 0x48); template -struct S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B { +struct S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B { S_ServerInitDefault_DC_PC_V3_02_17_91_9B basic_cmd; // This field is not part of SEGA's implementation; the client ignores it. // newserv sends a message here disavowing the preceding copyright notice. @@ -348,18 +345,19 @@ struct S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B { // Internal name: SndRegist struct C_LegacyLogin_PC_V3_03 { - le_uint64_t unused = 0; // Same as unused field in 9D/9E - le_uint32_t sub_version = 0; - uint8_t is_extended = 0; - uint8_t language = 0; - le_uint16_t unknown_a2 = 0; + /* 00 */ le_uint64_t unused = 0; // Same as unused field in 9D/9E + /* 08 */ le_uint32_t sub_version = 0; + /* 0C */ uint8_t is_extended = 0; + /* 0D */ uint8_t language = 0; + /* 0E */ le_uint16_t unknown_a2 = 0; // Note: These are suffixed with 2 since they come from the same source data // as the corresponding fields in 9D/9E. (Even though serial_number and // serial_number2 have the same contents in 9E, they do not come from the same // field on the client's connection context object.) - pstring serial_number2; - pstring access_key2; -} __packed__; + /* 10 */ pstring serial_number2; + /* 20 */ pstring access_key2; + /* 30 */ +} __packed_ws__(C_LegacyLogin_PC_V3_03, 0x30); // 03 (S->C): Legacy register result (non-BB) // Internal name: RcvRegist @@ -384,10 +382,10 @@ struct S_ServerInitDefault_BB_03_9B { pstring copyright; parray server_key; parray client_key; -} __packed__; +} __packed_ws__(S_ServerInitDefault_BB_03_9B, 0xC0); template -struct S_ServerInitWithAfterMessage_BB_03_9B { +struct S_ServerInitWithAfterMessageT_BB_03_9B { S_ServerInitDefault_BB_03_9B basic_cmd; // As in 02, this field is not part of SEGA's implementation. pstring after_message; @@ -402,20 +400,21 @@ struct S_ServerInitWithAfterMessage_BB_03_9B { // header.flag is nonzero, but it's not clear what it's used for. struct C_LegacyLogin_PC_V3_04 { - le_uint64_t unused1 = 0; // Same as unused field in 9D/9E - le_uint32_t sub_version = 0; - uint8_t is_extended = 0; - uint8_t language = 0; - le_uint16_t unknown_a2 = 0; - pstring serial_number; - pstring access_key; -} __packed__; + /* 00 */ le_uint64_t unused1 = 0; // Same as unused field in 9D/9E + /* 08 */ le_uint32_t sub_version = 0; + /* 0C */ uint8_t is_extended = 0; + /* 0D */ uint8_t language = 0; + /* 0E */ le_uint16_t unknown_a2 = 0; + /* 10 */ pstring serial_number; + /* 20 */ pstring access_key; + /* 30 */ +} __packed_ws__(C_LegacyLogin_PC_V3_04, 0x30); struct C_LegacyLogin_BB_04 { parray unknown_a1; pstring username; pstring password; -} __packed__; +} __packed_ws__(C_LegacyLogin_BB_04, 0x2C); // 04 (S->C): Set guild card number and update client config ("security data") // Internal name: RcvLogin @@ -455,7 +454,7 @@ struct S_UpdateClientConfig_DC_PC_04 { // player is present and zero when none is present. le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; -} __packed__; +} __packed_ws__(S_UpdateClientConfig_DC_PC_04, 8); struct S_UpdateClientConfig_V3_04 { le_uint32_t player_tag = 0x00010000; @@ -463,13 +462,13 @@ struct S_UpdateClientConfig_V3_04 { // This field is opaque to the client; it will send back the contents verbatim // in its next 9E command (or on request via 9F). parray client_config; -} __packed__; +} __packed_ws__(S_UpdateClientConfig_V3_04, 0x28); struct S_UpdateClientConfig_BB_04 { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; parray client_config; -} __packed__; +} __packed_ws__(S_UpdateClientConfig_BB_04, 0x30); // 05: Disconnect // Internal name: SndLogout @@ -516,16 +515,16 @@ struct S_UpdateClientConfig_BB_04 { // is not included in the count and does not appear on the client. The text of // the first entry becomes the ship name when the client joins a lobby. template -struct S_MenuEntry { +struct S_MenuEntryT { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; le_uint16_t flags = 0x0F04; // Should be this value, apparently pstring text; } __packed__; -struct S_MenuEntry_PC_BB_07_1F : S_MenuEntry { -} __packed__; -struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry { -} __packed__; +using S_MenuEntry_PC_BB_07_1F = S_MenuEntryT; +using S_MenuEntry_DC_V3_07_1F = S_MenuEntryT; +check_struct_size(S_MenuEntry_PC_BB_07_1F, 0x2C); +check_struct_size(S_MenuEntry_DC_V3_07_1F, 0x1C); // 08 (C->S): Request game list // Internal name: SndGameList @@ -538,7 +537,7 @@ struct S_MenuEntry_DC_V3_07_1F : S_MenuEntry { // Command is a list of these; header.flag is the entry count. The first entry // is not included in the count and does not appear on the client. template -struct S_GameMenuEntry { +struct S_GameMenuEntryT { le_uint32_t menu_id = 0; le_uint32_t game_id = 0; // difficulty_tag is 0x0A on Episode 3; on all other versions, it's @@ -561,10 +560,10 @@ struct S_GameMenuEntry { // 20 = Is challenge mode uint8_t flags = 0; } __packed__; -struct S_GameMenuEntry_PC_BB_08 : S_GameMenuEntry { -} __packed__; -struct S_GameMenuEntry_DC_V3_08_Ep3_E6 : S_GameMenuEntry { -} __packed__; +using S_GameMenuEntry_PC_BB_08 = S_GameMenuEntryT; +using S_GameMenuEntry_DC_V3_08_Ep3_E6 = S_GameMenuEntryT; +check_struct_size(S_GameMenuEntry_PC_BB_08, 0x2C); +check_struct_size(S_GameMenuEntry_DC_V3_08_Ep3_E6, 0x1C); // 09 (C->S): Menu item info request // Internal name: SndInfo @@ -574,7 +573,7 @@ struct S_GameMenuEntry_DC_V3_08_Ep3_E6 : S_GameMenuEntry { struct C_MenuItemInfoRequest_09 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(C_MenuItemInfoRequest_09, 8); // 0B: Invalid command @@ -603,43 +602,49 @@ struct C_MenuItemInfoRequest_09 { // second-phase handler is missing. template -struct SC_MeetUserExtension { +struct SC_MeetUserExtensionT { struct LobbyReference { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; - } __packed__; + } __packed_ws__(LobbyReference, 8); + /* 00 */ parray lobby_refs; /* 40 */ le_uint32_t unknown_a2 = 0; /* 44 */ pstring player_name; /* 64 (or 84 on UTF16 versions) */ } __packed__; +using SC_MeetUserExtension_DC_V3 = SC_MeetUserExtensionT; +using SC_MeetUserExtension_PC_BB = SC_MeetUserExtensionT; +check_struct_size(SC_MeetUserExtension_DC_V3, 0x64); +check_struct_size(SC_MeetUserExtension_PC_BB, 0x84); struct S_LegacyJoinGame_PC_0E { struct LobbyData { le_uint32_t player_tag = 0; le_uint32_t guild_card_number = 0; pstring name; - } __packed__; + } __packed_ws__(LobbyData, 0x18); + parray lobby_data; parray unknown_a3; -} __packed__; +} __packed_ws__(S_LegacyJoinGame_PC_0E, 0x80); struct S_LegacyJoinGame_GC_0E { parray lobby_data; - SC_MeetUserExtension meet_user_extension; + SC_MeetUserExtension_DC_V3 meet_user_extension; parray unknown_a3; -} __packed__; +} __packed_ws__(S_LegacyJoinGame_GC_0E, 0xE8); struct S_LegacyJoinGame_XB_0E { struct LobbyData { le_uint32_t player_tag = 0; le_uint32_t guild_card_number = 0; pstring name; - } __packed__; + } __packed_ws__(LobbyData, 0x20); parray lobby_data; - SC_MeetUserExtension meet_user_extension; + SC_MeetUserExtension_DC_V3 meet_user_extension; parray unknown_a3; -} __packed__; +} __packed_ws__(S_LegacyJoinGame_XB_0E, 0xE8); // 0F: Invalid command @@ -656,38 +661,38 @@ struct S_LegacyJoinGame_XB_0E { struct C_MenuSelection_10_Flag00 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(C_MenuSelection_10_Flag00, 8); template -struct C_MenuSelection_10_Flag01 { +struct C_MenuSelectionT_10_Flag01 { C_MenuSelection_10_Flag00 basic_cmd; pstring unknown_a1; } __packed__; -struct C_MenuSelection_DC_V3_10_Flag01 : C_MenuSelection_10_Flag01 { -} __packed__; -struct C_MenuSelection_PC_BB_10_Flag01 : C_MenuSelection_10_Flag01 { -} __packed__; +using C_MenuSelection_DC_V3_10_Flag01 = C_MenuSelectionT_10_Flag01; +using C_MenuSelection_PC_BB_10_Flag01 = C_MenuSelectionT_10_Flag01; +check_struct_size(C_MenuSelection_DC_V3_10_Flag01, 0x18); +check_struct_size(C_MenuSelection_PC_BB_10_Flag01, 0x28); template -struct C_MenuSelection_10_Flag02 { +struct C_MenuSelectionT_10_Flag02 { C_MenuSelection_10_Flag00 basic_cmd; pstring password; } __packed__; -struct C_MenuSelection_DC_V3_10_Flag02 : C_MenuSelection_10_Flag02 { -} __packed__; -struct C_MenuSelection_PC_BB_10_Flag02 : C_MenuSelection_10_Flag02 { -} __packed__; +using C_MenuSelection_DC_V3_10_Flag02 = C_MenuSelectionT_10_Flag02; +using C_MenuSelection_PC_BB_10_Flag02 = C_MenuSelectionT_10_Flag02; +check_struct_size(C_MenuSelection_DC_V3_10_Flag02, 0x18); +check_struct_size(C_MenuSelection_PC_BB_10_Flag02, 0x28); template -struct C_MenuSelection_10_Flag03 { +struct C_MenuSelectionT_10_Flag03 { C_MenuSelection_10_Flag00 basic_cmd; pstring unknown_a1; pstring password; } __packed__; -struct C_MenuSelection_DC_V3_10_Flag03 : C_MenuSelection_10_Flag03 { -} __packed__; -struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03 { -} __packed__; +using C_MenuSelection_DC_V3_10_Flag03 = C_MenuSelectionT_10_Flag03; +using C_MenuSelection_PC_BB_10_Flag03 = C_MenuSelectionT_10_Flag03; +check_struct_size(C_MenuSelection_DC_V3_10_Flag03, 0x28); +check_struct_size(C_MenuSelection_PC_BB_10_Flag03, 0x48); // 11 (S->C): Ship info // Internal name: RcvMessage @@ -723,7 +728,7 @@ struct S_WriteFile_13_A7 { pstring filename; parray data; le_uint32_t data_size = 0; -} __packed__; +} __packed_ws__(S_WriteFile_13_A7, 0x414); // 13 (C->S): Confirm file write (V3/BB) // Client sends this in response to each 13 sent by the server. It appears @@ -732,7 +737,7 @@ struct S_WriteFile_13_A7 { // header.flag = file chunk index (same as in the 13/A7 sent by the server) struct C_WriteFileConfirmation_V3_BB_13_A7 { pstring filename; -} __packed__; +} __packed_ws__(C_WriteFileConfirmation_V3_BB_13_A7, 0x10); // 14 (S->C): Valid but ignored (all versions) // Internal name: RcvUpLoad @@ -770,8 +775,8 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 { // Note: PSO XB seems to ignore the address field, which makes sense given its // networking architecture. -struct S_Reconnect_19 : S_Reconnect { -} __packed__; +using S_Reconnect_19 = S_ReconnectT; +check_struct_size(S_Reconnect_19, 8); // Because PSO PC and some versions of PSO DC/GC use the same port but different // protocols, we use a specially-crafted 19 command to send them to two @@ -788,7 +793,7 @@ struct S_ReconnectSplit_19 { be_uint32_t gc_address = 0; le_uint16_t gc_port = 0; parray unused2; -} __packed__; +} __packed_ws__(S_ReconnectSplit_19, 0xAC); // 1A (S->C): Large message box // Internal name: RcvText @@ -847,7 +852,7 @@ struct S_ReconnectSplit_19 { struct SC_GameGuardCheck_BB_0022 { parray data; -} __packed__; +} __packed_ws__(SC_GameGuardCheck_BB_0022, 0x10); // 0122 (C->S): Time deviation (BB) // This command is sent when the client executes a quest opcode 5D (gettime) and @@ -873,7 +878,7 @@ struct S_ExchangeSecretLotteryTicketResult_BB_24 { uint8_t start_index = 0; uint8_t unused = 0; parray unknown_a3; -} __packed__; +} __packed_ws__(S_ExchangeSecretLotteryTicketResult_BB_24, 0x24); // 25 (S->C): Gallon's Plan result (BB) // Sent in response to a 6xE1 command from the client. @@ -885,7 +890,7 @@ struct S_GallonPlanResult_BB_25 { uint8_t value1 = 0; uint8_t value2 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(S_GallonPlanResult_BB_25, 8); // 26: Invalid command // 27: Invalid command @@ -927,13 +932,13 @@ struct C_GuildCardSearch_40 { le_uint32_t player_tag = 0x00010000; le_uint32_t searcher_guild_card_number = 0; le_uint32_t target_guild_card_number = 0; -} __packed__; +} __packed_ws__(C_GuildCardSearch_40, 0x0C); // 41 (S->C): Guild card search result // Internal name: RcvUserAns template -struct S_GuildCardSearchResult { +struct S_GuildCardSearchResultT { le_uint32_t player_tag = 0x00010000; le_uint32_t searcher_guild_card_number = 0; le_uint32_t result_guild_card_number = 0; @@ -948,17 +953,14 @@ struct S_GuildCardSearchResult { // login command (9D/9E) after connecting to the server designated in // reconnect_command. When processing the 9D/9E, newserv uses only the // lobby_id field within, but it fills in all fields when sending a 41. - SC_MeetUserExtension extension; -} __packed__; -struct S_GuildCardSearchResult_PC_41 - : S_GuildCardSearchResult { -} __packed__; -struct S_GuildCardSearchResult_DC_V3_41 - : S_GuildCardSearchResult { -} __packed__; -struct S_GuildCardSearchResult_BB_41 - : S_GuildCardSearchResult { + SC_MeetUserExtensionT extension; } __packed__; +using S_GuildCardSearchResult_PC_41 = S_GuildCardSearchResultT; +using S_GuildCardSearchResult_DC_V3_41 = S_GuildCardSearchResultT; +using S_GuildCardSearchResult_BB_41 = S_GuildCardSearchResultT; +check_struct_size(S_GuildCardSearchResult_PC_41, 0x124); +check_struct_size(S_GuildCardSearchResult_DC_V3_41, 0xC0); +check_struct_size(S_GuildCardSearchResult_BB_41, 0x128); // 42: Invalid command // 43: Invalid command @@ -989,14 +991,14 @@ struct S_OpenFile_DC_44_A6 { uint8_t type = 0; pstring filename; le_uint32_t file_size = 0; -} __packed__; +} __packed_ws__(S_OpenFile_DC_44_A6, 0x38); struct S_OpenFile_PC_GC_44_A6 { pstring name; // Should begin with "PSO/" le_uint16_t type = 0; pstring filename; le_uint32_t file_size = 0; -} __packed__; +} __packed_ws__(S_OpenFile_PC_GC_44_A6, 0x38); // Curiously, PSO XB expects an extra 0x18 bytes at the end of this command, but // those extra bytes are unused, and the client does not fail if they're @@ -1005,7 +1007,7 @@ struct S_OpenFile_XB_44_A6 : S_OpenFile_PC_GC_44_A6 { pstring xb_filename; le_uint32_t content_meta; parray unused2; -} __packed__; +} __packed_ws__(S_OpenFile_XB_44_A6, 0x50); struct S_OpenFile_BB_44_A6 { parray unused; @@ -1013,7 +1015,7 @@ struct S_OpenFile_BB_44_A6 { pstring filename; le_uint32_t file_size = 0; pstring name; -} __packed__; +} __packed_ws__(S_OpenFile_BB_44_A6, 0x50); // 44 (C->S): Confirm open file (V3/BB) // Client sends this in response to each 44 sent by the server. @@ -1023,7 +1025,7 @@ struct S_OpenFile_BB_44_A6 { // > 0xFF so the flag is essentially meaningless) struct C_OpenFileConfirmation_44_A6 { pstring filename; -} __packed__; +} __packed_ws__(C_OpenFileConfirmation_44_A6, 0x10); // 45: Invalid command // 46: Invalid command @@ -1077,37 +1079,37 @@ struct C_OpenFileConfirmation_44_A6 { struct PlayerRecordsEntry_DC { /* 00 */ le_uint32_t client_id = 0; - /* 04 */ PlayerRecordsDC_Challenge challenge; - /* A4 */ PlayerRecords_Battle battle; + /* 04 */ PlayerRecordsChallengeDC challenge; + /* A4 */ PlayerRecordsBattle battle; /* BC */ -} __packed__; +} __packed_ws__(PlayerRecordsEntry_DC, 0xBC); struct PlayerRecordsEntry_PC { /* 00 */ le_uint32_t client_id = 0; - /* 04 */ PlayerRecordsPC_Challenge challenge; - /* DC */ PlayerRecords_Battle battle; + /* 04 */ PlayerRecordsChallengePC challenge; + /* DC */ PlayerRecordsBattle battle; /* F4 */ -} __packed__; +} __packed_ws__(PlayerRecordsEntry_PC, 0xF4); struct PlayerRecordsEntry_V3 { /* 0000 */ le_uint32_t client_id = 0; - /* 0004 */ PlayerRecordsV3_Challenge challenge; - /* 0104 */ PlayerRecords_Battle battle; + /* 0004 */ PlayerRecordsChallengeV3 challenge; + /* 0104 */ PlayerRecordsBattle battle; /* 011C */ -} __packed__; +} __packed_ws__(PlayerRecordsEntry_V3, 0x011C); struct PlayerRecordsEntry_BB { /* 0000 */ le_uint32_t client_id = 0; - /* 0004 */ PlayerRecordsBB_Challenge challenge; - /* 0144 */ PlayerRecords_Battle battle; + /* 0004 */ PlayerRecordsChallengeBB challenge; + /* 0144 */ PlayerRecordsBattle battle; /* 015C */ -} __packed__; +} __packed_ws__(PlayerRecordsEntry_BB, 0x015C); struct C_CharacterData_DCv1_61_98 { /* 0000 */ PlayerInventory inventory; /* 034C */ PlayerDispDataDCPCV3 disp; /* 041C */ -} __attribute__((packed)); +} __packed_ws__(C_CharacterData_DCv1_61_98, 0x041C); struct C_CharacterData_DCv2_61_98 { /* 0000 */ PlayerInventory inventory; @@ -1115,7 +1117,7 @@ struct C_CharacterData_DCv2_61_98 { /* 041C */ PlayerRecordsEntry_DC records; /* 04D8 */ ChoiceSearchConfig choice_search_config; /* 04F0 */ -} __attribute__((packed)); +} __packed_ws__(C_CharacterData_DCv2_61_98, 0x04F0); struct C_CharacterData_PC_61_98 { /* 0000 */ PlayerInventory inventory; @@ -1128,7 +1130,7 @@ struct C_CharacterData_PC_61_98 { // that, the client truncates the command after the first null value (rounded // up to the next 4-byte boundary). /* 05A4 */ // uint16_t auto_reply[...EOF]; -} __attribute__((packed)); +} __packed_ws__(C_CharacterData_PC_61_98, 0x5A4); struct C_CharacterData_GCNTE_61_98 { /* 0000 */ PlayerInventory inventory; @@ -1136,12 +1138,12 @@ struct C_CharacterData_GCNTE_61_98 { /* 041C */ PlayerRecordsEntry_DC records; /* 04D8 */ ChoiceSearchConfig choice_search_config; /* 04F0 */ parray blocked_senders; - /* 0468 */ le_uint32_t auto_reply_enabled = 0; + /* 0568 */ le_uint32_t auto_reply_enabled = 0; // 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). - /* 046C */ // char auto_reply[...EOF]; -} __attribute__((packed)); + /* 056C */ // char auto_reply[...EOF]; +} __packed_ws__(C_CharacterData_GCNTE_61_98, 0x56C); struct C_CharacterData_V3_61_98 { /* 0000 */ PlayerInventory inventory; @@ -1155,7 +1157,7 @@ struct C_CharacterData_V3_61_98 { // the client truncates the command after the first zero byte (rounded up to // the next 4-byte boundary). /* 0678 */ // char auto_reply[...EOF]; -} __attribute__((packed)); +} __packed_ws__(C_CharacterData_V3_61_98, 0x678); struct C_CharacterData_Ep3_61_98 { /* 0000 */ PlayerInventory inventory; @@ -1168,7 +1170,7 @@ struct C_CharacterData_Ep3_61_98 { /* 0678 */ pstring auto_reply; /* 0724 */ Episode3::PlayerConfig ep3_config; /* 2A74 */ -} __attribute__((packed)); +} __packed_ws__(C_CharacterData_Ep3_61_98, 0x2A74); struct C_CharacterData_BB_61_98 { /* 0000 */ PlayerInventory inventory; @@ -1180,8 +1182,8 @@ struct C_CharacterData_BB_61_98 { /* 0820 */ le_uint32_t auto_reply_enabled = 0; // Like on V3, the client truncates the command if the auto reply message is // shorter than 0x200 bytes. - /* 082C */ // uint16_t auto_reply[...EOF]; -} __attribute__((packed)); + /* 0824 */ // uint16_t auto_reply[...EOF]; +} __packed_ws__(C_CharacterData_BB_61_98, 0x824); // 62: Target command // Internal name: SndPsoData2 @@ -1210,7 +1212,7 @@ struct C_CharacterData_BB_61_98 { // Header flag = entry count template -struct S_JoinGame_DC_PC { +struct S_JoinGameT_DC_PC { // Note: It seems Sega servers sent uninitialized memory in the variations // field when sending this command to start an Episode 3 tournament game. This // can be misleading when reading old logs from those days, but the Episode 3 @@ -1238,17 +1240,16 @@ struct S_JoinGame_DCNTE_64 { uint8_t unused = 0; parray variations; parray lobby_data; -} __packed__; +} __packed_ws__(S_JoinGame_DCNTE_64, 0x104); +using S_JoinGame_DC_64 = S_JoinGameT_DC_PC; +using S_JoinGame_PC_64 = S_JoinGameT_DC_PC; +check_struct_size(S_JoinGame_DC_64, 0x10C); +check_struct_size(S_JoinGame_PC_64, 0x14C); -struct S_JoinGame_DC_64 : S_JoinGame_DC_PC { -} __packed__; -struct S_JoinGame_PC_64 : S_JoinGame_DC_PC { -} __packed__; - -struct S_JoinGame_GC_64 : S_JoinGame_DC_PC { +struct S_JoinGame_GC_64 : S_JoinGameT_DC_PC { uint8_t episode = 0; parray unused; -} __packed__; +} __packed_ws__(S_JoinGame_GC_64, 0x110); struct S_JoinGame_Ep3_64 : S_JoinGame_GC_64 { // This field is only present if the game (and client) is Episode 3. Similarly @@ -1257,22 +1258,22 @@ struct S_JoinGame_Ep3_64 : S_JoinGame_GC_64 { struct Ep3PlayerEntry { PlayerInventory inventory; PlayerDispDataDCPCV3 disp; - } __packed__; + } __packed_ws__(Ep3PlayerEntry, 0x41C); parray players_ep3; -} __packed__; +} __packed_ws__(S_JoinGame_Ep3_64, 0x1180); -struct S_JoinGame_XB_64 : S_JoinGame_DC_PC { +struct S_JoinGame_XB_64 : S_JoinGameT_DC_PC { uint8_t episode = 0; parray unused; parray unknown_a1; -} __packed__; +} __packed_ws__(S_JoinGame_XB_64, 0x1D8); -struct S_JoinGame_BB_64 : S_JoinGame_DC_PC { +struct S_JoinGame_BB_64 : S_JoinGameT_DC_PC { uint8_t episode = 0; uint8_t unused1 = 1; uint8_t solo_mode = 0; uint8_t unused2 = 0; -} __packed__; +} __packed_ws__(S_JoinGame_BB_64, 0x1A0); // 65 (S->C): Add player to game // Internal name: RcvBurstGame @@ -1285,7 +1286,7 @@ struct LobbyFlags_DCNTE { uint8_t leader_id = 0; uint8_t disable_udp = 1; uint8_t unused = 0; -} __packed__; +} __packed_ws__(LobbyFlags_DCNTE, 4); struct LobbyFlags { uint8_t client_id = 0; @@ -1297,11 +1298,11 @@ struct LobbyFlags { uint8_t event = 0; uint8_t unknown_a2 = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(LobbyFlags, 0x0C); // Header flag = entry count (always 1 for 65 and 68; up to 0x0C for 67) template -struct S_JoinLobby { +struct S_JoinLobbyT { LobbyFlagsT lobby_flags; struct Entry { LobbyDataT lobby_data; @@ -1313,22 +1314,17 @@ struct S_JoinLobby { parray entries; static inline size_t size(size_t used_entries) { - return offsetof(S_JoinLobby, entries) + used_entries * sizeof(Entry); + return offsetof(S_JoinLobbyT, entries) + used_entries * sizeof(Entry); } } __packed__; - -struct S_JoinLobby_DCNTE_65_67_68 - : S_JoinLobby { -} __packed__; -struct S_JoinLobby_PC_65_67_68 - : S_JoinLobby { -} __packed__; -struct S_JoinLobby_DC_GC_65_67_68_Ep3_EB - : S_JoinLobby { -} __packed__; -struct S_JoinLobby_BB_65_67_68 - : S_JoinLobby { -} __packed__; +using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT; +using S_JoinLobby_PC_65_67_68 = S_JoinLobbyT; +using S_JoinLobby_DC_GC_65_67_68_Ep3_EB = S_JoinLobbyT; +using S_JoinLobby_BB_65_67_68 = S_JoinLobbyT; +check_struct_size(S_JoinLobby_DCNTE_65_67_68, 0x32D4); +check_struct_size(S_JoinLobby_PC_65_67_68, 0x339C); +check_struct_size(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, 0x32DC); +check_struct_size(S_JoinLobby_BB_65_67_68, 0x3D8C); struct S_JoinLobby_XB_65_67_68 { LobbyFlags lobby_flags; @@ -1337,7 +1333,7 @@ struct S_JoinLobby_XB_65_67_68 { PlayerLobbyDataXB lobby_data; PlayerInventory inventory; PlayerDispDataDCPCV3 disp; - } __packed__; + } __packed_ws__(Entry, 0x468); // Note: not all of these will be filled in and sent if the lobby isn't full // (the command size will be shorter than this struct's size) parray entries; @@ -1345,7 +1341,7 @@ struct S_JoinLobby_XB_65_67_68 { static inline size_t size(size_t used_entries) { return offsetof(S_JoinLobby_XB_65_67_68, entries) + used_entries * sizeof(Entry); } -} __packed__; +} __packed_ws__(S_JoinLobby_XB_65_67_68, 0x3504); // 66 (S->C): Remove player from game // Internal name: RcvExitGame @@ -1359,7 +1355,7 @@ struct S_LeaveLobby_66_69_Ep3_E9 { // and spectator teams. uint8_t disable_udp = 1; uint8_t unused = 0; -} __packed__; +} __packed_ws__(S_LeaveLobby_66_69_Ep3_E9, 4); // 67 (S->C): Join lobby // Internal name: RcvStartLobby2 @@ -1431,13 +1427,13 @@ struct C_GenerateID_DCNTE_80 { uint8_t unused2 = 0; // Always 0 le_uint16_t unused3 = 0; // Always 0 parray unused4; // Client sends uninitialized data here -} __packed__; +} __packed_ws__(C_GenerateID_DCNTE_80, 0x0C); struct S_GenerateID_DC_PC_V3_80 { le_uint32_t client_id = 0; le_uint32_t unused = 0; le_uint32_t next_item_id = 0; -} __packed__; +} __packed_ws__(S_GenerateID_DC_PC_V3_80, 0x0C); // 81: Simple mail // Internal name: RcvChatMessage and SndChatMessage @@ -1457,7 +1453,7 @@ struct SC_SimpleMail_PC_81 { pstring from_name; le_uint32_t to_guild_card_number = 0; pstring text; -} __packed__; +} __packed_ws__(SC_SimpleMail_PC_81, 0x42C); struct SC_SimpleMail_DC_V3_81 { le_uint32_t player_tag = 0x00010000; @@ -1465,7 +1461,7 @@ struct SC_SimpleMail_DC_V3_81 { pstring from_name; le_uint32_t to_guild_card_number = 0; pstring text; -} __packed__; +} __packed_ws__(SC_SimpleMail_DC_V3_81, 0x21C); struct SC_SimpleMail_BB_81 { le_uint32_t player_tag = 0x00010000; @@ -1474,7 +1470,7 @@ struct SC_SimpleMail_BB_81 { le_uint32_t to_guild_card_number = 0; pstring received_date; pstring text; -} __packed__; +} __packed_ws__(SC_SimpleMail_BB_81, 0x454); // 82: Invalid command @@ -1499,7 +1495,7 @@ struct S_LobbyListEntry_83 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(S_LobbyListEntry_83, 0x0C); // 84 (C->S): Choose lobby // Internal name: SndRoomChange @@ -1507,7 +1503,7 @@ struct S_LobbyListEntry_83 { struct C_LobbySelection_84 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(C_LobbySelection_84, 8); // 85: Invalid command // 86: Invalid command @@ -1519,7 +1515,7 @@ struct C_LobbySelection_84 { struct C_Login_DCNTE_88 { pstring serial_number; pstring access_key; -} __packed__; +} __packed_ws__(C_Login_DCNTE_88, 0x22); // 88 (S->C): License check result (DC NTE only) // No arguments except header.flag. @@ -1554,7 +1550,7 @@ struct S_ArrowUpdateEntry_88 { // 0B - white // 0C - black le_uint32_t arrow_color = 0; -} __packed__; +} __packed_ws__(S_ArrowUpdateEntry_88, 0x0C); // 89 (C->S): Set lobby arrow // header.flag = arrow color number (see above); no other arguments. @@ -1573,7 +1569,7 @@ struct C_ConnectionInfo_DCNTE_8A { pstring username; pstring password; pstring email_address; // From Sylverant documentation -} __packed__; +} __packed_ws__(C_ConnectionInfo_DCNTE_8A, 0xA0); // 8A (S->C): Connection information result (DC NTE only) // header.flag is a success flag. If 0 is sent, the client shows an error @@ -1608,11 +1604,11 @@ struct C_Login_DCNTE_8B { pstring password; pstring name; parray unused; -} __packed__; +} __packed_ws__(C_Login_DCNTE_8B, 0xAC); struct C_LoginExtended_DCNTE_8B : C_Login_DCNTE_8B { - SC_MeetUserExtension extension; -} __packed__; + SC_MeetUserExtension_DC_V3 extension; +} __packed_ws__(C_LoginExtended_DCNTE_8B, 0x110); // 8C: Invalid command @@ -1639,7 +1635,7 @@ struct C_LoginV1_DC_PC_V3_90 { // cause the client to send this command despite its size not being a // multiple of 4. This is fixed in later versions, so we handle both cases in // the receive handler. -} __packed__; +} __packed_ws__(C_LoginV1_DC_PC_V3_90, 0x22); // 90 (S->C): License verification result (V3) // Behaves exactly the same as 9A (S->C). No arguments except header.flag. @@ -1665,7 +1661,7 @@ struct C_RegisterV1_DC_92 { pstring hardware_id; pstring unknown_a4; pstring email; // According to Sylverant documentation -} __packed__; +} __packed_ws__(C_RegisterV1_DC_92, 0xA0); // 92 (S->C): Register result (non-BB) // Internal name: RcvPsoRegist @@ -1688,11 +1684,11 @@ struct C_LoginV1_DC_93 { pstring unknown_a3; pstring name; parray unused2; -} __packed__; +} __packed_ws__(C_LoginV1_DC_93, 0xAC); struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 { - SC_MeetUserExtension extension; -} __packed__; + SC_MeetUserExtension_DC_V3 extension; +} __packed_ws__(C_LoginExtendedV1_DC_93, 0x110); // 93 (C->S): Log in (BB) @@ -1716,12 +1712,12 @@ struct C_LoginBase_BB_93 { pstring username; pstring password; - // These fields map to the same fields in SC_MeetUserExtension. There is no + // These fields map to the same fields in SC_MeetUserExtensionT. There is no // equivalent of the name field from that structure on BB (though newserv // doesn't use it anyway). le_uint32_t menu_id = 0; le_uint32_t preferred_lobby_id = 0; -} __packed__; +} __packed_ws__(C_LoginBase_BB_93, 0x7C); struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 { // Note: Unlike other versions, BB puts the version string in the client @@ -1729,14 +1725,14 @@ struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 { // will be something like "Ver. 1.24.3". This format is used on older client // versions (before 1.23.8?) parray client_config; -} __packed__; +} __packed_ws__(C_LoginWithoutHardwareInfo_BB_93, 0xA4); struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 { // See the comment in the above structure. This format is used on newer client // versions. parray hardware_info; parray client_config; -} __packed__; +} __packed_ws__(C_LoginWithHardwareInfo_BB_93, 0xAC); // 94: Invalid command @@ -1768,7 +1764,7 @@ struct C_CharSaveInfo_DCv2_PC_V3_BB_96 { // for each character and could therefore tell if a character had connected to // an unofficial server between connections to Sega's servers. le_uint32_t event_counter = 0; -} __packed__; +} __packed_ws__(C_CharSaveInfo_DCv2_PC_V3_BB_96, 8); // 97 (S->C): Save to memory card // Internal name: RcvSaveCountCheck @@ -1815,7 +1811,7 @@ struct C_Login_DC_PC_V3_9A { pstring serial_number2; // On DCv2, this is the hardware ID pstring access_key2; pstring email_address; -} __packed__; +} __packed_ws__(C_Login_DC_PC_V3_9A, 0xDC); // 9A (S->C): License verification result // Internal name: RcvPsoRegistCheckV2 @@ -1866,7 +1862,7 @@ struct C_Register_DC_PC_V3_9C { pstring serial_number; // On XB, this is the XBL gamertag pstring access_key; // On XB, this is the XBL user ID pstring password; // On XB, this contains "xbox-pso" -} __packed__; +} __packed_ws__(C_Register_DC_PC_V3_9C, 0xA0); struct C_Register_BB_9C { le_uint32_t sub_version = 0; @@ -1876,7 +1872,7 @@ struct C_Register_BB_9C { pstring username; pstring password; pstring game_tag; // "psopc2" on BB -} __packed__; +} __packed_ws__(C_Register_BB_9C, 0x98); // 9C (S->C): Register result // Internal name: RcvPsoRegistV2 @@ -1895,29 +1891,31 @@ struct C_Register_BB_9C { // by its menu ID and item ID. struct C_Login_DC_PC_GC_9D { - /* 04 */ le_uint32_t player_tag = 0x00010000; // 0x00010000 if guild card is set (via 04) - /* 08 */ le_uint32_t guild_card_number = 0; // 0xFFFFFFFF if not set - /* 0C */ le_uint32_t unused1 = 0; - /* 10 */ le_uint32_t unused2 = 0; - /* 14 */ le_uint32_t sub_version = 0; - /* 18 */ uint8_t is_extended = 0; // If 1, structure has extended format - /* 19 */ uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES - /* 1A */ parray unused3; // Always zeroes - /* 1C */ pstring v1_serial_number; - /* 2C */ pstring v1_access_key; - /* 3C */ pstring serial_number; // On XB, this is the XBL gamertag - /* 4C */ pstring access_key; // On XB, this is the XBL user ID - /* 5C */ pstring serial_number2; // On XB, this is the XBL gamertag - /* 8C */ pstring access_key2; // On XB, this is the XBL user ID - /* BC */ pstring name; - /* CC */ -} __packed__; + /* 00 */ le_uint32_t player_tag = 0x00010000; // 0x00010000 if guild card is set (via 04) + /* 04 */ le_uint32_t guild_card_number = 0; // 0xFFFFFFFF if not set + /* 08 */ le_uint32_t unused1 = 0; + /* 0C */ le_uint32_t unused2 = 0; + /* 10 */ le_uint32_t sub_version = 0; + /* 14 */ uint8_t is_extended = 0; // If 1, structure has extended format + /* 15 */ uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES + /* 16 */ parray unused3; // Always zeroes + /* 18 */ pstring v1_serial_number; + /* 28 */ pstring v1_access_key; + /* 38 */ pstring serial_number; // On XB, this is the XBL gamertag + /* 48 */ pstring access_key; // On XB, this is the XBL user ID + /* 58 */ pstring serial_number2; // On XB, this is the XBL gamertag + /* 88 */ pstring access_key2; // On XB, this is the XBL user ID + /* B8 */ pstring name; + /* C8 */ +} __packed_ws__(C_Login_DC_PC_GC_9D, 0xC8); + struct C_LoginExtended_DC_GC_9D : C_Login_DC_PC_GC_9D { - SC_MeetUserExtension extension; -} __packed__; + SC_MeetUserExtension_DC_V3 extension; +} __packed_ws__(C_LoginExtended_DC_GC_9D, 0x12C); + struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D { - SC_MeetUserExtension extension; -} __packed__; + SC_MeetUserExtension_PC_BB extension; +} __packed_ws__(C_LoginExtended_PC_9D, 0x14C); // 9E (C->S): Log in with client config (V3/BB) // Not used on GC Episodes 1&2 Trial Edition. @@ -1930,10 +1928,11 @@ struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D { struct C_Login_GC_9E : C_Login_DC_PC_GC_9D { parray client_config; -} __packed__; +} __packed_ws__(C_Login_GC_9E, 0xE8); + struct C_LoginExtended_GC_9E : C_Login_GC_9E { - SC_MeetUserExtension extension; -} __packed__; + SC_MeetUserExtension_DC_V3 extension; +} __packed_ws__(C_LoginExtended_GC_9E, 0x14C); struct C_Login_XB_9E : C_Login_DC_PC_GC_9D { parray unused; @@ -1942,27 +1941,29 @@ struct C_Login_XB_9E : C_Login_DC_PC_GC_9D { le_uint32_t xb_user_id_high = 0; le_uint32_t xb_user_id_low = 0; le_uint32_t unknown_a1b = 0; -} __packed__; +} __packed_ws__(C_Login_XB_9E, 0x130); + struct C_LoginExtended_XB_9E : C_Login_XB_9E { - SC_MeetUserExtension extension; -} __packed__; + SC_MeetUserExtension_DC_V3 extension; +} __packed_ws__(C_LoginExtended_XB_9E, 0x194); struct C_LoginExtended_BB_9E { - le_uint32_t player_tag = 0x00010000; - le_uint32_t guild_card_number = 0; // == serial_number when on newserv - le_uint32_t sub_version = 0; - le_uint32_t unknown_a1 = 0; - le_uint32_t unknown_a2 = 0; - pstring unknown_a3; // Always blank? - pstring unknown_a4; // == "?" - pstring unknown_a5; // Always blank? - pstring unknown_a6; // Always blank? - pstring username; - pstring password; - pstring guild_card_number_str; - parray unknown_a7; - SC_MeetUserExtension extension; -} __packed__; + /* 0000 */ le_uint32_t player_tag = 0x00010000; + /* 0004 */ le_uint32_t guild_card_number = 0; // == serial_number when on newserv + /* 0008 */ le_uint32_t sub_version = 0; + /* 000C */ le_uint32_t unknown_a1 = 0; + /* 0010 */ le_uint32_t unknown_a2 = 0; + /* 0014 */ pstring unknown_a3; // Always blank? + /* 0024 */ pstring unknown_a4; // == "?" + /* 0034 */ pstring unknown_a5; // Always blank? + /* 0044 */ pstring unknown_a6; // Always blank? + /* 0054 */ pstring username; + /* 0084 */ pstring password; + /* 00B4 */ pstring guild_card_number_str; + /* 00C4 */ parray unknown_a7; + /* 00EC */ SC_MeetUserExtension_PC_BB extension; + /* 0170 */ +} __packed_ws__(C_LoginExtended_BB_9E, 0x170); // 9F (S->C): Request client config / security data (V3/BB) // This command is not valid on PSO GC Episodes 1&2 Trial Edition, nor any @@ -1976,11 +1977,11 @@ struct C_LoginExtended_BB_9E { struct C_ClientConfig_V3_9F { parray data; -} __packed__; +} __packed_ws__(C_ClientConfig_V3_9F, 0x20); struct C_ClientConfig_BB_9F { parray data; -} __packed__; +} __packed_ws__(C_ClientConfig_BB_9F, 0x28); // A0 (C->S): Change ship // Internal name: SndShipList @@ -1990,7 +1991,7 @@ struct C_ChangeShipOrBlock_A0_A1 { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; parray unused; -} __packed__; +} __packed_ws__(C_ChangeShipOrBlock_A0_A1, 0x18); // A0 (S->C): Ship select menu // Same as 07 command. @@ -2012,7 +2013,7 @@ struct C_ChangeShipOrBlock_A0_A1 { // data with 44/13 commands; for A9, the server does not need to respond. template -struct S_QuestMenuEntry { +struct S_QuestMenuEntryT { // Note: The game treats menu_id as two 8-bit fields followed by a 16-bit // field. In most situations, this is opaque to the server, so we treat it as // a single 32-bit field, but in the case of the quest menu, the second byte @@ -2027,12 +2028,12 @@ struct S_QuestMenuEntry { pstring name; pstring short_description; } __packed__; -struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry { -} __packed__; -struct S_QuestMenuEntry_DC_GC_A2_A4 : S_QuestMenuEntry { -} __packed__; -struct S_QuestMenuEntry_XB_A2_A4 : S_QuestMenuEntry { -} __packed__; +using S_QuestMenuEntry_PC_A2_A4 = S_QuestMenuEntryT; +using S_QuestMenuEntry_DC_GC_A2_A4 = S_QuestMenuEntryT; +using S_QuestMenuEntry_XB_A2_A4 = S_QuestMenuEntryT; +check_struct_size(S_QuestMenuEntry_PC_A2_A4, 0x128); +check_struct_size(S_QuestMenuEntry_DC_GC_A2_A4, 0x98); +check_struct_size(S_QuestMenuEntry_XB_A2_A4, 0xA8); struct S_QuestMenuEntry_BB_A2_A4 { le_uint32_t menu_id = 0; @@ -2044,7 +2045,7 @@ struct S_QuestMenuEntry_BB_A2_A4 { // This field is ignored if the icon type (see S_QuestMenuEntry) isn't 1 or 2. uint8_t disabled = 0; parray unused; -} __packed__; +} __packed_ws__(S_QuestMenuEntry_BB_A2_A4, 0x13C); // A3 (S->C): Quest information // Same format as 1A/D5 command (plain text) @@ -2103,7 +2104,7 @@ struct C_SendQuestStatistic_V3_BB_AA { le_uint16_t function_id1 = 0; le_uint16_t function_id2 = 0; parray params; -} __packed__; +} __packed_ws__(C_SendQuestStatistic_V3_BB_AA, 0x28); // AB (S->C): Call quest function (V3/BB) // This command is not valid on PSO GC Episodes 1&2 Trial Edition. @@ -2115,7 +2116,7 @@ struct C_SendQuestStatistic_V3_BB_AA { struct S_CallQuestFunction_V3_BB_AB { le_uint16_t function_id = 0; parray unused; -} __packed__; +} __packed_ws__(S_CallQuestFunction_V3_BB_AB, 4); // AC: Quest barrier (V3/BB) // No arguments; header.flag must be 0 (or else the client disconnects) @@ -2170,7 +2171,7 @@ struct S_ServerTime_B1 { /* 1A */ uint8_t time_flags_mid = 0x00; /* 1B */ uint8_t time_flags_high = 0x00; /* 1C */ -} __packed__; +} __packed_ws__(S_ServerTime_B1, 0x1C); // B2 (S->C): Execute code and/or checksum memory (DCv2 and all later versions) // Internal name: RcvProgramPatch @@ -2201,10 +2202,10 @@ struct S_ExecuteCode_B2 { le_uint32_t checksum_start = 0; // May be null if size is zero le_uint32_t checksum_size = 0; // If zero, no checksum is computed // The code immediately follows, ending with an S_ExecuteCode_Footer_B2 -} __packed__; +} __packed_ws__(S_ExecuteCode_B2, 0x0C); template -struct S_ExecuteCode_Footer_B2 { +struct S_ExecuteCode_FooterT_B2 { using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; @@ -2235,11 +2236,10 @@ struct S_ExecuteCode_Footer_B2 { U32T entrypoint_addr_offset = 0; // Relative to code base (after checksum_size). parray unused2; } __packed__; - -struct S_ExecuteCode_Footer_GC_B2 : S_ExecuteCode_Footer_B2 { -} __packed__; -struct S_ExecuteCode_Footer_DC_PC_XB_BB_B2 : S_ExecuteCode_Footer_B2 { -} __packed__; +using S_ExecuteCode_Footer_GC_B2 = S_ExecuteCode_FooterT_B2; +using S_ExecuteCode_Footer_DC_PC_XB_BB_B2 = S_ExecuteCode_FooterT_B2; +check_struct_size(S_ExecuteCode_Footer_GC_B2, 0x20); +check_struct_size(S_ExecuteCode_Footer_DC_PC_XB_BB_B2, 0x20); // B3 (C->S): Execute code and/or checksum memory result // Not used on versions that don't support the B2 command (see above). @@ -2252,7 +2252,7 @@ struct C_ExecuteCodeResult_B3 { // If code_size was 0 in the B2 command, return_value is always 0. le_uint32_t return_value = 0; le_uint32_t checksum = 0; // 0 if no checksum was computed -} __packed__; +} __packed_ws__(C_ExecuteCodeResult_B3, 8); // B4: Invalid command // B5: Invalid command @@ -2269,7 +2269,7 @@ struct S_RankUpdate_Ep3_B7 { le_uint32_t current_meseta = 0; le_uint32_t total_meseta_earned = 0; le_uint32_t unlocked_jukebox_songs = 0xFFFFFFFF; -} __packed__; +} __packed_ws__(S_RankUpdate_Ep3_B7, 0x1C); // B7 (C->S): Confirm rank update (Episode 3) // No arguments @@ -2353,7 +2353,7 @@ struct S_UpdateMediaHeader_Ep3_B9 { // of the compressed data is 0x3800 bytes, and it must decompress to fewer // than 0x37000 bytes of output. If either of these limits are violated, the // client ignores the command. -} __packed__; +} __packed_ws__(S_UpdateMediaHeader_Ep3_B9, 0x0C); // B9 (C->S): Confirm received B9 (Episode 3) // No arguments @@ -2375,13 +2375,13 @@ struct C_MesetaTransaction_Ep3_BA { le_uint32_t transaction_num = 0; le_uint32_t value = 0; le_uint32_t request_token = 0; -} __packed__; +} __packed_ws__(C_MesetaTransaction_Ep3_BA, 0x0C); struct S_MesetaTransaction_Ep3_BA { le_uint32_t current_meseta = 0; le_uint32_t total_meseta_earned = 0; le_uint32_t request_token = 0; // Should match the token sent by the client -} __packed__; +} __packed_ws__(S_MesetaTransaction_Ep3_BA, 0x0C); // BB (S->C): Tournament match information (Episode 3) // This command is not valid on Episode 3 Trial Edition. Because of this, it @@ -2397,7 +2397,7 @@ struct S_TournamentMatchInformation_Ep3_BB { le_uint16_t win_count = 0; le_uint16_t is_active = 0; pstring name; - } __packed__; + } __packed_ws__(TeamEntry, 0x24); parray team_entries; le_uint16_t num_teams = 0; le_uint16_t unknown_a3 = 0; // Probably actually unused @@ -2407,9 +2407,9 @@ struct S_TournamentMatchInformation_Ep3_BB { uint8_t count = 0; uint8_t max_count = 0; uint8_t unused = 0; - } __packed__; + } __packed_ws__(MatchEntry, 0x24); parray match_entries; -} __packed__; +} __packed_ws__(S_TournamentMatchInformation_Ep3_BB, 0xDA4); // BC: Invalid command // BD: Invalid command @@ -2426,17 +2426,17 @@ struct S_TournamentMatchInformation_Ep3_BB { // Command is a list of these; header.flag is the entry count (incl. top-level). template -struct S_ChoiceSearchEntry { +struct S_ChoiceSearchEntryT { // Category IDs are nonzero; if the high byte of the ID is nonzero then the // category can be set by the user at any time; otherwise it can't. le_uint16_t parent_choice_id = 0; // 0 for top-level categories le_uint16_t choice_id = 0; pstring text; } __packed__; -struct S_ChoiceSearchEntry_DC_V3_C0 : S_ChoiceSearchEntry { -} __packed__; -struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { -} __packed__; +using S_ChoiceSearchEntry_DC_V3_C0 = S_ChoiceSearchEntryT; +using S_ChoiceSearchEntry_PC_BB_C0 = S_ChoiceSearchEntryT; +check_struct_size(S_ChoiceSearchEntry_DC_V3_C0, 0x20); +check_struct_size(S_ChoiceSearchEntry_PC_BB_C0, 0x3C); // Top-level categories are things like "Level", "Class", etc. // Choices for each top-level category immediately follow the category, so @@ -2455,7 +2455,7 @@ struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { // Internal name: SndCreateGame template -struct C_CreateGame_DCNTE { +struct C_CreateGameBaseT { // menu_id and item_id are only used for the E7 (create spectator team) form // of this command le_uint32_t menu_id = 0; @@ -2463,9 +2463,11 @@ struct C_CreateGame_DCNTE { pstring name; pstring password; } __packed__; +using C_CreateGame_DCNTE = C_CreateGameBaseT; +check_struct_size(C_CreateGame_DCNTE, 0x28); template -struct C_CreateGame : C_CreateGame_DCNTE { +struct C_CreateGameT : C_CreateGameBaseT { uint8_t difficulty = 0; // 0-3 (always 0 on Episode 3) uint8_t battle_mode = 0; // 0 or 1 (always 0 on Episode 3) // Note: Episode 3 uses the challenge mode flag for view battle permissions. @@ -2476,15 +2478,15 @@ struct C_CreateGame : C_CreateGame_DCNTE { // players; if set to 1, it's v2-only. uint8_t episode = 0; // 1-4 on V3+ (3 on Episode 3); unused on DC/PC } __packed__; -struct C_CreateGame_DC_V3_0C_C1_Ep3_EC : C_CreateGame { -} __packed__; -struct C_CreateGame_PC_C1 : C_CreateGame { -} __packed__; +using C_CreateGame_DC_V3_0C_C1_Ep3_EC = C_CreateGameT; +using C_CreateGame_PC_C1 = C_CreateGameT; +check_struct_size(C_CreateGame_DC_V3_0C_C1_Ep3_EC, 0x2C); +check_struct_size(C_CreateGame_PC_C1, 0x4C); -struct C_CreateGame_BB_C1 : C_CreateGame { +struct C_CreateGame_BB_C1 : C_CreateGameT { uint8_t solo_mode = 0; parray unused2; -} __packed__; +} __packed_ws__(C_CreateGame_BB_C1, 0x50); // C2 (C->S): Set choice search parameters (DCv2 and later versions) // Internal name: PutChoiceList @@ -2504,7 +2506,7 @@ struct C_CreateGame_BB_C1 : C_CreateGame { // Command is a list of these; header.flag is the entry count template -struct S_ChoiceSearchResultEntry_C4 { +struct S_ChoiceSearchResultEntryT_C4 { le_uint32_t guild_card_number = 0; pstring name; pstring info_string; // Usually something like " Lvl " @@ -2514,15 +2516,14 @@ struct S_ChoiceSearchResultEntry_C4 { pstring location_string; HeaderT reconnect_command_header; // Ignored by the client S_Reconnect_19 reconnect_command; - SC_MeetUserExtension meet_user; -} __packed__; - -struct S_ChoiceSearchResultEntry_DC_V3_C4 : S_ChoiceSearchResultEntry_C4 { -} __packed__; -struct S_ChoiceSearchResultEntry_PC_C4 : S_ChoiceSearchResultEntry_C4 { -} __packed__; -struct S_ChoiceSearchResultEntry_BB_C4 : S_ChoiceSearchResultEntry_C4 { + SC_MeetUserExtensionT meet_user; } __packed__; +using S_ChoiceSearchResultEntry_DC_V3_C4 = S_ChoiceSearchResultEntryT_C4; +using S_ChoiceSearchResultEntry_PC_C4 = S_ChoiceSearchResultEntryT_C4; +using S_ChoiceSearchResultEntry_BB_C4 = S_ChoiceSearchResultEntryT_C4; +check_struct_size(S_ChoiceSearchResultEntry_DC_V3_C4, 0xD4); +check_struct_size(S_ChoiceSearchResultEntry_PC_C4, 0x154); +check_struct_size(S_ChoiceSearchResultEntry_BB_C4, 0x158); // C5 (S->C): Player records update (DCv2 and later versions) // Internal name: RcvChallengeData @@ -2536,14 +2537,13 @@ struct S_ChoiceSearchResultEntry_BB_C4 : S_ChoiceSearchResultEntry_C4 -struct C_SetBlockedSenders_C6 { +struct C_SetBlockedSendersT_C6 { parray blocked_senders; } __packed__; - -struct C_SetBlockedSenders_V3_C6 : C_SetBlockedSenders_C6<30> { -} __packed__; -struct C_SetBlockedSenders_BB_C6 : C_SetBlockedSenders_C6<28> { -} __packed__; +using C_SetBlockedSenders_V3_C6 = C_SetBlockedSendersT_C6<30>; +using C_SetBlockedSenders_BB_C6 = C_SetBlockedSendersT_C6<28>; +check_struct_size(C_SetBlockedSenders_V3_C6, 0x78); +check_struct_size(C_SetBlockedSenders_BB_C6, 0x70); // C7 (C->S): Enable simple mail auto-reply (V3/BB) // Same format as 1A/D5 command (plain text). @@ -2605,9 +2605,9 @@ struct S_ConfirmTournamentEntry_Ep3_CC { le_uint16_t win_count = 0; le_uint16_t is_active = 0; pstring name; - } __packed__; + } __packed_ws__(TeamEntry, 0x24); parray team_entries; -} __packed__; +} __packed_ws__(S_ConfirmTournamentEntry_Ep3_CC, 0x508); // CD: Invalid command // CE: Invalid command @@ -2642,7 +2642,7 @@ struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server // command. newserv parses and regenerates the item data when sending D3, // which effectively erases the uninitialized data. parray item_datas; -} __packed__; +} __packed_ws__(SC_TradeItems_D0_D3, 0x284); // D1 (S->C): Advance trade state (V3/BB) // No arguments @@ -2694,7 +2694,7 @@ struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server struct C_GBAGameRequest_V3_D7 { pstring filename; -} __packed__; +} __packed_ws__(C_GBAGameRequest_V3_D7, 0x10); // D7 (S->C): GBA file not found (V3/BB) // No arguments @@ -2714,14 +2714,14 @@ struct C_GBAGameRequest_V3_D7 { // Command is a list of these; header.flag is the entry count. There should be // one entry for each player in the current lobby/game. template -struct S_InfoBoardEntry_D8 { +struct S_InfoBoardEntryT_D8 { pstring name; pstring message; } __packed__; -struct S_InfoBoardEntry_BB_D8 : S_InfoBoardEntry_D8 { -} __packed__; -struct S_InfoBoardEntry_V3_D8 : S_InfoBoardEntry_D8 { -} __packed__; +using S_InfoBoardEntry_V3_D8 = S_InfoBoardEntryT_D8; +using S_InfoBoardEntry_BB_D8 = S_InfoBoardEntryT_D8; +check_struct_size(S_InfoBoardEntry_V3_D8, 0xBC); +check_struct_size(S_InfoBoardEntry_BB_D8, 0x178); // D9 (C->S): Write info board (V3/BB) // Contents are plain text, like 1A/D5. @@ -2743,7 +2743,7 @@ struct C_VerifyLicense_V3_DB { pstring serial_number2; // On XB, this is the XBL gamertag pstring access_key2; // On XB, this is the XBL user ID pstring password; // On XB, this contains "xbox-pso" -} __packed__; +} __packed_ws__(C_VerifyLicense_V3_DB, 0xDC); // Note: This login pathway generally isn't used on BB (and isn't supported at // all during the data server phase). All current servers use 03/93 instead. @@ -2757,7 +2757,7 @@ struct C_VerifyLicense_BB_DB { pstring username; pstring password; pstring game_tag; // "psopc2" -} __packed__; +} __packed_ws__(C_VerifyLicense_BB_DB, 0xD4); // DC: Set battle in progress flag (Episode 3) // No arguments except header.flag when sent by the client. When header.flag is @@ -2772,19 +2772,19 @@ struct S_GuildCardHeader_BB_01DC { le_uint32_t unknown = 1; le_uint32_t filesize = 0x0000D590; le_uint32_t checksum = 0; // CRC32 of entire guild card file (0xD590 bytes) -} __packed__; +} __packed_ws__(S_GuildCardHeader_BB_01DC, 0x0C); struct S_GuildCardFileChunk_02DC { le_uint32_t unknown = 0; // 0 le_uint32_t chunk_index = 0; parray data; // Command may be shorter if this is the last chunk -} __packed__; +} __packed_ws__(S_GuildCardFileChunk_02DC, 0x6808); struct C_GuildCardDataRequest_BB_03DC { le_uint32_t unknown = 0; le_uint32_t chunk_index = 0; le_uint32_t cont = 0; -} __packed__; +} __packed_ws__(C_GuildCardDataRequest_BB_03DC, 0x0C); // DD (S->C): Send quest state to joining player (BB) // When a player joins a game with a quest already in progress, the server @@ -2798,45 +2798,45 @@ struct C_GuildCardDataRequest_BB_03DC { struct S_RareMonsterList_BB_DE { // Unused entries are set to FFFF parray enemy_indexes; -} __packed__; +} __packed_ws__(S_RareMonsterList_BB_DE, 0x20); // DF (C->S): Set Challenge Mode parameters (BB) // This command has 7 subcommands, most of which are self-explanatory. struct C_SetChallengeModeStageNumber_BB_01DF { le_uint32_t stage = 0; -} __packed__; +} __packed_ws__(C_SetChallengeModeStageNumber_BB_01DF, 4); struct C_SetChallengeModeCharacterTemplate_BB_02DF { le_uint32_t template_index = 0; -} __packed__; +} __packed_ws__(C_SetChallengeModeCharacterTemplate_BB_02DF, 4); struct C_SetChallengeModeDifficulty_BB_03DF { // No existing challenge mode quest sets this to a value other than zero. le_uint32_t difficulty = 0; -} __packed__; +} __packed_ws__(C_SetChallengeModeDifficulty_BB_03DF, 4); struct C_SetChallengeModeEXPMultiplier_BB_04DF { le_float exp_multiplier = 1.0f; -} __packed__; +} __packed_ws__(C_SetChallengeModeEXPMultiplier_BB_04DF, 4); struct C_SetChallengeRankText_BB_05DF { le_uint32_t rank_color = 0; // ARGB8888 pstring rank_text; -} __packed__; +} __packed_ws__(C_SetChallengeRankText_BB_05DF, 0x1C); // This is sent once for each rank (so, 3 times in total) struct C_SetChallengeRankThreshold_BB_06DF { le_uint32_t rank = 0; // 0 = B, 1 = A, 2 = S le_uint32_t seconds = 0; le_uint32_t rank_bitmask = 0; // 1 = B, 2 = A, 4 = S -} __packed__; +} __packed_ws__(C_SetChallengeRankThreshold_BB_06DF, 0x0C); struct C_CreateChallengeModeAwardItem_BB_07DF { le_uint32_t prize_rank = 0xFFFFFFFF; // 0 = B, 1 = A, 2 = S, anything else = error le_uint32_t rank_bitmask = 0; // Same as in 06DF ItemData item; -} __packed__; +} __packed_ws__(C_CreateChallengeModeAwardItem_BB_07DF, 0x1C); // E0 (S->C): Tournament list (Episode 3) // The client will send 09 and 10 commands to inspect or enter a tournament. The @@ -2856,9 +2856,9 @@ struct S_TournamentList_Ep3NTE_E0 { pstring name; le_uint16_t num_teams = 0; le_uint16_t max_teams = 0; - } __packed__; + } __packed_ws__(Entry, 0x34); parray entries; -} __packed__; +} __packed_ws__(S_TournamentList_Ep3NTE_E0, 0x680); struct S_TournamentList_Ep3_E0 { struct Entry { @@ -2890,9 +2890,9 @@ struct S_TournamentList_Ep3_E0 { le_uint16_t max_teams = 0; le_uint16_t unknown_a3 = 0xFFFF; le_uint16_t unknown_a4 = 0xFFFF; - } __packed__; + } __packed_ws__(Entry, 0x38); parray entries; -} __packed__; +} __packed_ws__(S_TournamentList_Ep3_E0, 0x700); // E0 (C->S): Request system file (BB) // No arguments. The server should respond with an E1 or E2 command. @@ -2906,23 +2906,22 @@ struct S_TournamentList_Ep3_E0 { // flag value means. template -struct S_GameInformationBase_Ep3_E1 { - /* 0004 */ pstring game_name; +struct S_GameInformationBaseT_Ep3_E1 { + /* 0000 */ pstring game_name; struct PlayerEntry { pstring name; // From disp.name pstring description; // Usually something like "FOmarl CLv30 J" - } __packed__; - /* 0024 */ parray player_entries; - /* 00E4 */ pstring map_name; - /* 0104 */ RulesT rules; - /* 0118 */ parray spectator_entries; - /* 0298 */ -} __packed__; - -struct S_GameInformation_Ep3NTE_E1 : S_GameInformationBase_Ep3_E1 { -} __packed__; -struct S_GameInformation_Ep3_E1 : S_GameInformationBase_Ep3_E1 { + } __packed_ws__(PlayerEntry, 0x30); + /* 0020 */ parray player_entries; + /* 00E0 */ pstring map_name; + /* 0100 */ RulesT rules; + /* 0114 */ parray spectator_entries; + /* 0294 */ } __packed__; +using S_GameInformation_Ep3NTE_E1 = S_GameInformationBaseT_Ep3_E1; +using S_GameInformation_Ep3_E1 = S_GameInformationBaseT_Ep3_E1; +check_struct_size(S_GameInformation_Ep3NTE_E1, 0x28C); +check_struct_size(S_GameInformation_Ep3_E1, 0x294); // E1 (S->C): System file created (BB) // This seems to take the place of 00E2 in certain cases. Perhaps it was used @@ -2934,7 +2933,7 @@ struct S_SystemFileCreated_00E1_BB { // server disconnect (907)" and disconnects. Otherwise, the client proceeeds // as if it had received an 00E2 command, and sends its first 00E3. le_uint32_t success = 1; -} __packed__; +} __packed_ws__(S_SystemFileCreated_00E1_BB, 4); // E2 (C->S): Tournament control (Episode 3) // No arguments (in any of its forms) except header.flag, which determines ths @@ -2981,9 +2980,9 @@ struct S_TournamentEntryList_Ep3_E2 { uint8_t state = 0; uint8_t unknown_a2 = 0; pstring name; - } __packed__; + } __packed_ws__(Entry, 0x2C); parray entries; -} __packed__; +} __packed_ws__(S_TournamentEntryList_Ep3_E2, 0x584); // E2 (S->C): Set system file contents (BB) // See PSOBBFullSystemFile in SaveFileFormats.hh for format @@ -3011,11 +3010,11 @@ struct S_TournamentEntryList_Ep3_E2 { // more appropriate for inspecting non-tournament games. template -struct S_TournamentGameDetailsBase_Ep3_E3 { +struct S_TournamentGameDetailsBaseT_Ep3_E3 { // These fields are used only if the Rules pane is shown - /* 0004 */ pstring name; - /* 0024 */ pstring map_name; - /* 0044 */ RulesT rules; + /* 0000 */ pstring name; + /* 0020 */ pstring map_name; + /* 0040 */ RulesT rules; // This field is used only if the bracket pane is shown struct BracketEntry { @@ -3023,8 +3022,8 @@ struct S_TournamentGameDetailsBase_Ep3_E3 { le_uint16_t is_active = 0; pstring team_name; parray unused; - } __packed__; - /* 0058 */ parray bracket_entries; + } __packed_ws__(BracketEntry, 0x24); + /* 0054 */ parray bracket_entries; // This field is used only if the Opponents pane is shown. If players_per_team // is 2, all fields are shown; if player_per_team is 1, team_name and @@ -3032,32 +3031,31 @@ struct S_TournamentGameDetailsBase_Ep3_E3 { struct PlayerEntry { pstring name; pstring description; // Usually something like "RAmarl CLv24 E" - } __packed__; + } __packed_ws__(PlayerEntry, 0x30); struct TeamEntry { pstring team_name; parray players; - } __packed__; - /* 04D8 */ parray team_entries; + } __packed_ws__(TeamEntry, 0x70); + /* 04D4 */ parray team_entries; - /* 05B8 */ le_uint16_t num_bracket_entries = 0; - /* 05BA */ le_uint16_t players_per_team = 0; - /* 05BC */ le_uint16_t unknown_a4 = 0; - /* 05BE */ le_uint16_t num_spectators = 0; - /* 05C0 */ parray spectator_entries; - /* 0740 */ -} __packed__; - -struct S_TournamentGameDetails_Ep3NTE_E3 : S_TournamentGameDetailsBase_Ep3_E3 { -} __packed__; -struct S_TournamentGameDetails_Ep3_E3 : S_TournamentGameDetailsBase_Ep3_E3 { + /* 05B4 */ le_uint16_t num_bracket_entries = 0; + /* 05B6 */ le_uint16_t players_per_team = 0; + /* 05B8 */ le_uint16_t unknown_a4 = 0; + /* 05BA */ le_uint16_t num_spectators = 0; + /* 05BC */ parray spectator_entries; + /* 073C */ } __packed__; +using S_TournamentGameDetails_Ep3NTE_E3 = S_TournamentGameDetailsBaseT_Ep3_E3; +using S_TournamentGameDetails_Ep3_E3 = S_TournamentGameDetailsBaseT_Ep3_E3; +check_struct_size(S_TournamentGameDetails_Ep3NTE_E3, 0x734); +check_struct_size(S_TournamentGameDetails_Ep3_E3, 0x73C); // E3 (C->S): Player preview request (BB) struct C_PlayerPreviewRequest_BB_E3 { le_int32_t character_index = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(C_PlayerPreviewRequest_BB_E3, 0x08); // E4: CARD lobby battle table state (Episode 3) // When client sends an E4, server should respond with another E4 (but these @@ -3071,7 +3069,7 @@ struct C_PlayerPreviewRequest_BB_E3 { struct C_CardBattleTableState_Ep3_E4 { le_uint16_t table_number = 0; le_uint16_t seat_number = 0; -} __packed__; +} __packed_ws__(C_CardBattleTableState_Ep3_E4, 4); // header.flag = table number struct S_CardBattleTableState_Ep3_E4 { @@ -3084,21 +3082,21 @@ struct S_CardBattleTableState_Ep3_E4 { le_uint16_t state = 0; le_uint16_t unknown_a1 = 0; le_uint32_t guild_card_number = 0; - } __packed__; + } __packed_ws__(Entry, 8); parray entries; -} __packed__; +} __packed_ws__(S_CardBattleTableState_Ep3_E4, 0x20); // E4 (S->C): Player choice or no player present (BB) struct S_ApprovePlayerChoice_BB_00E4 { le_int32_t character_index = 0; le_uint32_t result = 0; // 1 = approved -} __packed__; +} __packed_ws__(S_ApprovePlayerChoice_BB_00E4, 8); struct S_PlayerPreview_NoPlayer_BB_00E4 { le_int32_t character_index = 0; le_uint32_t error = 0; // 2 = no player present -} __packed__; +} __packed_ws__(S_PlayerPreview_NoPlayer_BB_00E4, 8); // E5 (C->S): Confirm CARD lobby battle table choice (Episode 3) // header.flag specifies whether the client answered "Yes" (1) or "No" (0). @@ -3106,7 +3104,7 @@ struct S_PlayerPreview_NoPlayer_BB_00E4 { struct S_CardBattleTableConfirmation_Ep3_E5 { le_uint16_t table_number = 0; le_uint16_t seat_number = 0; -} __packed__; +} __packed_ws__(S_CardBattleTableConfirmation_Ep3_E5, 4); // E5 (S->C): Player preview (BB) // E5 (C->S): Create character (BB) @@ -3114,7 +3112,7 @@ struct S_CardBattleTableConfirmation_Ep3_E5 { struct SC_PlayerPreview_CreateCharacter_BB_00E5 { le_int32_t character_index = 0; PlayerDispDataBBPreview preview; -} __packed__; +} __packed_ws__(SC_PlayerPreview_CreateCharacter_BB_00E5, 0x80); // E6 (C->S): Spectator team control (Episode 3) @@ -3127,7 +3125,7 @@ struct SC_PlayerPreview_CreateCharacter_BB_00E5 { struct C_JoinSpectatorTeam_Ep3_E6_Flag01 { le_uint32_t menu_id = 0; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(C_JoinSpectatorTeam_Ep3_E6_Flag01, 8); // E6 (S->C): Spectator team list (Episode 3) // Same format as 08 command. @@ -3149,7 +3147,7 @@ struct S_ClientInit_BB_00E6 { uint8_t can_create_team = 1; uint8_t episode_4_unlocked = 1; parray unused; -} __packed__; +} __packed_ws__(S_ClientInit_BB_00E6, 0x3C); // E7 (C->S): Create spectator team (Episode 3) // This command is used to create speectator teams for both tournaments and @@ -3162,7 +3160,7 @@ struct C_CreateSpectatorTeam_Ep3_E7 { pstring name; pstring password; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(C_CreateSpectatorTeam_Ep3_E7, 0x2C); // E7 (S->C): Tournament entry list for spectating (Episode 3) // Same format as E2 command. @@ -3173,7 +3171,7 @@ struct SC_SyncSaveFiles_BB_E7 { /* 0000 */ PSOBBCharacterFile char_file; /* 2EA4 */ PSOBBFullSystemFile system_file; /* 3994 */ -} __packed__; +} __packed_ws__(SC_SyncSaveFiles_BB_E7, 0x3994); // E8 (S->C): Join spectator team (Episode 3) // header.flag = player count (including spectators) @@ -3181,25 +3179,25 @@ struct SC_SyncSaveFiles_BB_E7 { // primary game's players should be the leader (this is what newserv does). struct S_JoinSpectatorTeam_Ep3_E8 { - /* 0004 */ parray variations; // unused + /* 0000 */ parray variations; // unused struct PlayerEntry { /* 0000 */ PlayerLobbyDataDCGC lobby_data; /* 0020 */ PlayerInventory inventory; /* 036C */ PlayerDispDataDCPCV3 disp; /* 043C */ - } __packed__; - /* 0084 */ parray players; - /* 1174 */ uint8_t client_id = 0; - /* 1175 */ uint8_t leader_id = 0; - /* 1176 */ uint8_t disable_udp = 1; - /* 1177 */ uint8_t difficulty = 0; - /* 1178 */ uint8_t battle_mode = 0; - /* 1179 */ uint8_t event = 0; - /* 117A */ uint8_t section_id = 0; - /* 117B */ uint8_t challenge_mode = 0; - /* 117C */ le_uint32_t rare_seed = 0; - /* 1180 */ uint8_t episode = 0; - /* 1181 */ parray unused; + } __packed_ws__(PlayerEntry, 0x43C); + /* 0080 */ parray players; + /* 1170 */ uint8_t client_id = 0; + /* 1171 */ uint8_t leader_id = 0; + /* 1172 */ uint8_t disable_udp = 1; + /* 1173 */ uint8_t difficulty = 0; + /* 1174 */ uint8_t battle_mode = 0; + /* 1175 */ uint8_t event = 0; + /* 1176 */ uint8_t section_id = 0; + /* 1177 */ uint8_t challenge_mode = 0; + /* 1178 */ le_uint32_t rare_seed = 0; + /* 117C */ uint8_t episode = 0; + /* 117D */ parray unused; struct SpectatorEntry { // It seems that at some point Sega intended to show each player's rank in // spectator teams. The unused1 and unused3 fields are intended for the @@ -3217,17 +3215,17 @@ struct S_JoinSpectatorTeam_Ep3_E8 { /* 30 */ le_uint32_t name_color = 0xFFFFFFFF; // ARGB8888 /* 34 */ parray unused4; /* 38 */ - } __packed__; + } __packed_ws__(SpectatorEntry, 0x38); // Somewhat misleadingly, this array also includes the players actually in the // battle - they appear in the first positions. Presumably the first 4 are // always for battlers, and the last 8 are always for spectators. - /* 1184 */ parray entries; - /* 1424 */ pstring spectator_team_name; + /* 1180 */ parray entries; + /* 1420 */ pstring spectator_team_name; // This field doesn't appear to be actually used by the game, but some servers // send it anyway (and the game ignores it) - /* 1444 */ parray spectator_players; - /* 3624 */ -} __packed__; + /* 1440 */ parray spectator_players; + /* 3620 */ +} __packed_ws__(S_JoinSpectatorTeam_Ep3_E8, 0x3620); // E8 (C->S): Guild card commands (BB) @@ -3238,7 +3236,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 { struct C_GuildCardChecksum_01E8 { le_uint32_t checksum = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(C_GuildCardChecksum_01E8, 8); // 02E8 (S->C): Accept/decline guild card file checksum // If needs_update is nonzero, the client will request the guild card file by @@ -3249,7 +3247,7 @@ struct C_GuildCardChecksum_01E8 { struct S_GuildCardChecksumResponse_BB_02E8 { le_uint32_t needs_update = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(S_GuildCardChecksumResponse_BB_02E8, 8); // 03E8 (C->S): Request guild card file // No arguments @@ -3262,7 +3260,7 @@ struct S_GuildCardChecksumResponse_BB_02E8 { struct C_DeleteGuildCard_BB_05E8_08E8 { le_uint32_t guild_card_number = 0; -} __packed__; +} __packed_ws__(C_DeleteGuildCard_BB_05E8_08E8, 4); // 06E8 (C->S): Update (overwrite) guild card // Note: This command is also sent when the player writes a comment on their own @@ -3280,14 +3278,14 @@ struct C_DeleteGuildCard_BB_05E8_08E8 { struct C_WriteGuildCardComment_BB_09E8 { le_uint32_t guild_card_number = 0; pstring comment; -} __packed__; +} __packed_ws__(C_WriteGuildCardComment_BB_09E8, 0xB4); // 0AE8 (C->S): Swap positions of guild cards in list struct C_SwapGuildCardPositions_BB_0AE8 { le_uint32_t guild_card_number1 = 0; le_uint32_t guild_card_number2 = 0; -} __packed__; +} __packed_ws__(C_SwapGuildCardPositions_BB_0AE8, 8); // E9 (S->C): Remove player from spectator team (Episode 3) // Same format as 66/69 commands. Like 69 (and unlike 66), the disable_udp field @@ -3307,7 +3305,7 @@ struct C_SwapGuildCardPositions_BB_0AE8 { struct S_TimedMessageBoxHeader_Ep3_EA { le_uint32_t duration = 0; // In frames; 30 frames = 1 second // Message data follows here (up to 0x1000 chars) -} __packed__; +} __packed_ws__(S_TimedMessageBoxHeader_Ep3_EA, 4); // EA: Team control (BB) @@ -3315,7 +3313,7 @@ struct S_TimedMessageBoxHeader_Ep3_EA { struct C_CreateTeam_BB_01EA { pstring name; -} __packed__; +} __packed_ws__(C_CreateTeam_BB_01EA, 0x20); // 02EA (S->C): Create team result // No arguments except header.flag, which specifies the error code. Values: @@ -3332,7 +3330,7 @@ struct C_CreateTeam_BB_01EA { struct C_AddOrRemoveTeamMember_BB_03EA_05EA { le_uint32_t guild_card_number = 0; -} __packed__; +} __packed_ws__(C_AddOrRemoveTeamMember_BB_03EA_05EA, 4); // 04EA (S->C): Add team member result // No arguments except header.flag, which specifies the error code. Values: @@ -3354,7 +3352,7 @@ struct SC_TeamChat_BB_07EA { pstring sender_name; // Text follows here. The message is truncated by the client if it is longer // than 0x8F wchar_ts. -} __packed__; +} __packed_ws__(SC_TeamChat_BB_07EA, 0x20); // 08EA (C->S): Get team member list // No arguments @@ -3369,10 +3367,10 @@ struct S_TeamMemberList_BB_09EA { le_uint32_t privilege_level = 0; // 0x10 or 0x20 = green, 0x30 = blue, 0x40 = red, anything else = white le_uint32_t guild_card_number = 0; pstring name; - } __packed__; + } __packed_ws__(Entry, 0x2C); // Variable-length field: // Entry entries[entry_count]; -} __packed__; +} __packed_ws__(S_TeamMemberList_BB_09EA, 4); // 0CEA (S->C): Unknown // The client appears to ignore this command. @@ -3380,7 +3378,7 @@ struct S_TeamMemberList_BB_09EA { struct S_Unknown_BB_0CEA { parray unknown_a1; // Text follows here -} __packed__; +} __packed_ws__(S_Unknown_BB_0CEA, 0x20); // 0DEA (C->S): Get team name // No arguments @@ -3390,14 +3388,14 @@ struct S_Unknown_BB_0CEA { struct S_TeamName_BB_0EEA { parray unused; pstring team_name; -} __packed__; +} __packed_ws__(S_TeamName_BB_0EEA, 0x30); // 0FEA (C->S): Set team flag // The client also accepts this command but completely ignores it. struct C_SetTeamFlag_BB_0FEA { parray flag_data; -} __packed__; +} __packed_ws__(C_SetTeamFlag_BB_0FEA, 0x800); // 10EA: Delete team (C->S) and result (S->C) // No arguments (C->S) @@ -3412,7 +3410,7 @@ struct C_SetTeamFlag_BB_0FEA { struct C_ChangeTeamMemberPrivilegeLevel_BB_11EA { le_uint32_t guild_card_number = 0; -} __packed__; +} __packed_ws__(C_ChangeTeamMemberPrivilegeLevel_BB_11EA, 4); // 12EA (S->C): Team membership information // If the client is not in a team, all fields should be zero. @@ -3428,7 +3426,7 @@ struct S_TeamMembershipInformation_BB_12EA { uint8_t unknown_a8 = 0; uint8_t unknown_a9 = 0; pstring team_name; -} __packed__; +} __packed_ws__(S_TeamMembershipInformation_BB_12EA, 0x38); // 13EA: Team info for lobby players // header.flag specifies the number of entries. @@ -3450,7 +3448,7 @@ struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry { /* 003C */ pstring player_name; /* 005C */ parray flag_data; /* 085C */ -} __packed__; +} __packed_ws__(S_TeamInfoForPlayer_BB_13EA_15EA_Entry, 0x85C); // 14EA (C->S): Get team info for lobby players // No arguments. Client always sends 1 in the header.flag field. @@ -3478,10 +3476,10 @@ struct S_IntraTeamRanking_BB_18EA { /* 0C */ pstring player_name; /* 2C */ le_uint32_t points = 0; /* 30 */ - } __packed__; + } __packed_ws__(Entry, 0x30); // Variable-length field: /* 0010 */ // Entry entries[num_entries]; -} __packed__; +} __packed_ws__(S_IntraTeamRanking_BB_18EA, 0x10); // 19EA: Team reward list // No arguments (C->S) @@ -3494,10 +3492,10 @@ struct S_TeamRewardList_BB_19EA_1AEA { /* 0180 */ le_uint32_t team_points = 0; /* 0184 */ le_uint32_t reward_id = 0; /* 0188 */ - } __packed__; + } __packed_ws__(Entry, 0x188); // Variable length field: // Entry entries[num_entries]; -} __packed__; +} __packed_ws__(S_TeamRewardList_BB_19EA_1AEA, 4); // 1AEA: Team rewards available for purchase // Same format as 19EA. @@ -3516,10 +3514,10 @@ struct S_CrossTeamRanking_BB_1CEA { /* 20 */ le_uint32_t team_points = 0; /* 24 */ le_uint32_t unknown_a1 = 0; /* 28 */ - } __packed__; + } __packed_ws__(Entry, 0x28); // Variable length field: // Entry entries[num_entries]; -} __packed__; +} __packed_ws__(S_CrossTeamRanking_BB_1CEA, 4); // 1DEA (S->C): Update team rewards bitmask // header.flag specifies the new rewards bitmask. @@ -3529,7 +3527,7 @@ struct S_CrossTeamRanking_BB_1CEA { struct C_RenameTeam_BB_1EEA { pstring new_team_name; -} __packed__; +} __packed_ws__(C_RenameTeam_BB_1EEA, 0x20); // 1FEA (S->C): Rename team result // This command behaves like 02EA, but is sent in response to 1EEA instead. @@ -3557,14 +3555,14 @@ struct S_StreamFileIndexEntry_BB_01EB { le_uint32_t checksum = 0; // CRC32 of file data le_uint32_t offset = 0; // offset in stream (== sum of all previous files' sizes) pstring filename; -} __packed__; +} __packed_ws__(S_StreamFileIndexEntry_BB_01EB, 0x4C); // 02EB (S->C): Send stream file chunk (BB) struct S_StreamFileChunk_BB_02EB { le_uint32_t chunk_index = 0; parray data; -} __packed__; +} __packed_ws__(S_StreamFileChunk_BB_02EB, 0x6804); // 03EB (C->S): Request a specific stream file chunk // header.flag is the chunk index. Server should respond with a 02EB command. @@ -3584,7 +3582,7 @@ struct C_LeaveCharacterSelect_BB_00EC { // 1 = recreate character // 2 = dressing room le_uint32_t reason = 0; -} __packed__; +} __packed_ws__(C_LeaveCharacterSelect_BB_00EC, 4); // ED (S->C): Force leave lobby/game (Episode 3) // No arguments @@ -3602,28 +3600,35 @@ struct C_LeaveCharacterSelect_BB_00EC { struct C_UpdateOptionFlags_BB_01ED { le_uint32_t option_flags = 0; -} __packed__; +} __packed_ws__(C_UpdateOptionFlags_BB_01ED, 4); + struct C_UpdateSymbolChats_BB_02ED { - parray symbol_chats; -} __packed__; + parray symbol_chats; +} __packed_ws__(C_UpdateSymbolChats_BB_02ED, 0x4E0); + struct C_UpdateChatShortcuts_BB_03ED { - parray chat_shortcuts; -} __packed__; + parray chat_shortcuts; +} __packed_ws__(C_UpdateChatShortcuts_BB_03ED, 0xA40); + struct C_UpdateKeyConfig_BB_04ED { parray key_config; -} __packed__; +} __packed_ws__(C_UpdateKeyConfig_BB_04ED, 0x16C); + struct C_UpdatePadConfig_BB_05ED { parray pad_config; -} __packed__; +} __packed_ws__(C_UpdatePadConfig_BB_05ED, 0x38); + struct C_UpdateTechMenu_BB_06ED { parray tech_menu; -} __packed__; +} __packed_ws__(C_UpdateTechMenu_BB_06ED, 0x28); + struct C_UpdateCustomizeMenu_BB_07ED { parray customize; -} __packed__; +} __packed_ws__(C_UpdateCustomizeMenu_BB_07ED, 0xE8); + struct C_UpdateChallengeRecords_BB_08ED { - PlayerRecordsBB_Challenge records; -} __packed__; + PlayerRecordsChallengeBB records; +} __packed_ws__(C_UpdateChallengeRecords_BB_08ED, 0x140); // EE: Trade cards (Episode 3) // This command has different forms depending on the header.flag value; the flag @@ -3638,14 +3643,14 @@ struct SC_TradeCards_Ep3_EE_FlagD0_FlagD3 { struct Entry { le_uint32_t card_type = 0; le_uint32_t count = 0; - } __packed__; + } __packed_ws__(Entry, 8); parray entries; -} __packed__; +} __packed_ws__(SC_TradeCards_Ep3_EE_FlagD0_FlagD3, 0x24); // EE D1 (S->C): Advance trade state struct S_AdvanceCardTradeState_Ep3_EE_FlagD1 { le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(S_AdvanceCardTradeState_Ep3_EE_FlagD1, 4); // EE D2 (C->S): Trade can proceed // No arguments @@ -3658,7 +3663,7 @@ struct S_AdvanceCardTradeState_Ep3_EE_FlagD1 { struct S_CardTradeComplete_Ep3_EE_FlagD4 { le_uint32_t success = 0; // 0 = failed, 1 = success, anything else = invalid -} __packed__; +} __packed_ws__(S_CardTradeComplete_Ep3_EE_FlagD4, 4); // EE (S->C): Scrolling message (BB) // Same format as 01. The message appears at the top of the screen and slowly @@ -3678,9 +3683,9 @@ struct S_StartCardAuction_Ep3_EF { struct Entry { le_uint16_t card_id = 0xFFFF; // Must be < 0x02F1 le_uint16_t min_price = 0; // Must be > 0 and < 100 - } __packed__; + } __packed_ws__(Entry, 4); parray entries; -} __packed__; +} __packed_ws__(S_StartCardAuction_Ep3_EF, 0x54); // EF (S->C): Set or disable shutdown command (BB) // All variants of EF except 00EF cause the given Windows shell command to be @@ -3697,7 +3702,7 @@ struct S_StartCardAuction_Ep3_EF { struct S_SetShutdownCommand_BB_01EF { pstring command; -} __packed__; +} __packed_ws__(S_SetShutdownCommand_BB_01EF, 0x200); // F0 (S->C): Force update player lobby data (BB) // Format is PlayerLobbyDataBB (in PlayerSubordinates.hh). This command @@ -3794,30 +3799,30 @@ struct G_ClientIDHeader { uint8_t subcommand = 0; uint8_t size = 0; le_uint16_t client_id = 0; // <= 12 -} __packed__; +} __packed_ws__(G_ClientIDHeader, 4); struct G_EnemyIDHeader { uint8_t subcommand = 0; uint8_t size = 0; le_uint16_t enemy_id = 0; // In [0x1000, 0x4000); not the same as enemy_index! -} __packed__; +} __packed_ws__(G_EnemyIDHeader, 4); struct G_ObjectIDHeader { uint8_t subcommand = 0; uint8_t size = 0; le_uint16_t object_id = 0; // >= 0x4000, != 0xFFFF -} __packed__; +} __packed_ws__(G_ObjectIDHeader, 4); struct G_ParameterHeader { uint8_t subcommand = 0; uint8_t size = 0; le_uint16_t param = 0; -} __packed__; +} __packed_ws__(G_ParameterHeader, 4); struct G_UnusedHeader { uint8_t subcommand = 0; uint8_t size = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_UnusedHeader, 4); template -struct G_ExtendedHeader { +struct G_ExtendedHeaderT { HeaderT basic_header; le_uint32_t size = 0; } __packed__; @@ -3839,7 +3844,7 @@ struct G_Unknown_6x04 { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x04, 8); // 6x05: Switch state changed // Some things that don't look like switches are implemented as switches using @@ -3861,7 +3866,7 @@ struct G_SwitchStateChanged_6x05 { // 01 - set unlock flag (if not set, the flag is cleared instead) // 02 - play room unlock sound if floor matches client's floor uint8_t flags = 0; -} __packed__; +} __packed_ws__(G_SwitchStateChanged_6x05, 0x0C); // 6x06: Send guild card @@ -3869,33 +3874,33 @@ struct G_SendGuildCard_DCNTE_6x06 { G_UnusedHeader header; GuildCardDCNTE guild_card; uint8_t unused; -} __packed__; +} __packed_ws__(G_SendGuildCard_DCNTE_6x06, 0x80); struct G_SendGuildCard_DC_6x06 { G_UnusedHeader header; GuildCardDC guild_card; parray unused; -} __packed__; +} __packed_ws__(G_SendGuildCard_DC_6x06, 0x84); struct G_SendGuildCard_PC_6x06 { G_UnusedHeader header; GuildCardPC guild_card; -} __packed__; +} __packed_ws__(G_SendGuildCard_PC_6x06, 0xF4); struct G_SendGuildCard_GC_6x06 { G_UnusedHeader header; GuildCardGC guild_card; -} __packed__; +} __packed_ws__(G_SendGuildCard_GC_6x06, 0x94); struct G_SendGuildCard_XB_6x06 { G_UnusedHeader header; GuildCardXB guild_card; -} __packed__; +} __packed_ws__(G_SendGuildCard_XB_6x06, 0x230); struct G_SendGuildCard_BB_6x06 { G_UnusedHeader header; GuildCardBB guild_card; -} __packed__; +} __packed_ws__(G_SendGuildCard_BB_6x06, 0x10C); // 6x07: Symbol chat @@ -3903,7 +3908,7 @@ struct G_SymbolChat_6x07 { G_UnusedHeader header; le_uint32_t client_id = 0; SymbolChat data; -} __packed__; +} __packed_ws__(G_SymbolChat_6x07, 0x44); // 6x08: Invalid subcommand @@ -3911,25 +3916,24 @@ struct G_SymbolChat_6x07 { struct G_Unknown_6x09 { G_EnemyIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x09, 4); // 6x0A: Update enemy state template -struct G_UpdateEnemyState_6x0A { +struct G_UpdateEnemyStateT_6x0A { G_EnemyIDHeader header; le_uint16_t enemy_index = 0; // [0, 0xB50) le_uint16_t total_damage = 0; // Flags: // 00000400 - should play hit animation // 00000800 - is dead - typename std::conditional::type flags = 0; -} __packed__; - -struct G_UpdateEnemyState_GC_6x0A : G_UpdateEnemyState_6x0A { -} __packed__; -struct G_UpdateEnemyState_DC_PC_XB_BB_6x0A : G_UpdateEnemyState_6x0A { + typename std::conditional_t flags = 0; } __packed__; +using G_UpdateEnemyState_GC_6x0A = G_UpdateEnemyStateT_6x0A; +using G_UpdateEnemyState_DC_PC_XB_BB_6x0A = G_UpdateEnemyStateT_6x0A; +check_struct_size(G_UpdateEnemyState_GC_6x0A, 0x0C); +check_struct_size(G_UpdateEnemyState_DC_PC_XB_BB_6x0A, 0x0C); // 6x0B: Update object state @@ -3937,7 +3941,7 @@ struct G_UpdateObjectState_6x0B { G_ClientIDHeader header; le_uint32_t flags = 0; le_uint32_t object_index = 0; -} __packed__; +} __packed_ws__(G_UpdateObjectState_6x0B, 0x0C); // 6x0C: Add condition (poison/slow/etc.) (protected on V3/V4) // 6x0D: Remove condition (poison/slow/etc.) (protected on V3/V4) @@ -3946,13 +3950,13 @@ struct G_AddOrRemoveCondition_6x0C_6x0D { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; // Probably condition type le_uint32_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_AddOrRemoveCondition_6x0C_6x0D, 0x0C); // 6x0E: Unknown (protected on V3/V4) struct G_Unknown_6x0E { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x0E, 4); // 6x0F: Invalid subcommand @@ -3963,7 +3967,7 @@ struct G_Unknown_6x10_6x11_6x12_6x14 { le_uint16_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; le_uint32_t unknown_a4 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x10_6x11_6x12_6x14, 0x0C); // 6x11: Unknown (not valid on Episode 3) // Same format as 6x10 @@ -3971,7 +3975,7 @@ struct G_Unknown_6x10_6x11_6x12_6x14 { // 6x12: Dragon boss actions (not valid on Episode 3) template -struct G_DragonBossActions_6x12 { +struct G_DragonBossActionsT_6x12 { using F32T = typename std::conditional::type; G_EnemyIDHeader header; le_uint16_t unknown_a2 = 0; @@ -3980,11 +3984,10 @@ struct G_DragonBossActions_6x12 { F32T x = 0.0f; F32T z = 0.0f; } __packed__; - -struct G_DragonBossActions_DC_PC_XB_BB_6x12 : G_DragonBossActions_6x12 { -} __packed__; -struct G_DragonBossActions_GC_6x12 : G_DragonBossActions_6x12 { -} __packed__; +using G_DragonBossActions_DC_PC_XB_BB_6x12 = G_DragonBossActionsT_6x12; +using G_DragonBossActions_GC_6x12 = G_DragonBossActionsT_6x12; +check_struct_size(G_DragonBossActions_DC_PC_XB_BB_6x12, 0x14); +check_struct_size(G_DragonBossActions_GC_6x12, 0x14); // 6x13: De Rol Le boss actions (not valid on Episode 3) @@ -3992,7 +3995,7 @@ struct G_DeRolLeBossActions_6x13 { G_EnemyIDHeader header; le_uint16_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; -} __packed__; +} __packed_ws__(G_DeRolLeBossActions_6x13, 8); // 6x14: De Rol Le boss actions (not valid on Episode 3) // Same format as 6x10 @@ -4005,7 +4008,7 @@ struct G_VolOptBossActions_6x15 { le_uint16_t unknown_a3 = 0; le_uint16_t unknown_a4 = 0; le_uint16_t unknown_a5 = 0; -} __packed__; +} __packed_ws__(G_VolOptBossActions_6x15, 0x0C); // 6x16: Vol Opt boss actions (not valid on Episode 3) @@ -4013,7 +4016,7 @@ struct G_VolOptBossActions_6x16 { G_UnusedHeader header; parray unknown_a2; le_uint16_t unknown_a3 = 0; -} __packed__; +} __packed_ws__(G_VolOptBossActions_6x16, 0x0C); // 6x17: Vol Opt phase 2 boss actions (not valid on Episode 3) @@ -4023,14 +4026,14 @@ struct G_VolOpt2BossActions_6x17 { le_float unknown_a3 = 0.0f; le_float unknown_a4 = 0.0f; le_uint32_t unknown_a5 = 0; -} __packed__; +} __packed_ws__(G_VolOpt2BossActions_6x17, 0x14); // 6x18: Vol Opt phase 2 boss actions (not valid on Episode 3) struct G_VolOpt2BossActions_6x18 { G_ClientIDHeader header; parray unknown_a2; -} __packed__; +} __packed_ws__(G_VolOpt2BossActions_6x18, 0x0C); // 6x19: Dark Falz boss actions (not valid on Episode 3) @@ -4040,7 +4043,7 @@ struct G_DarkFalzActions_6x19 { le_uint16_t unknown_a3 = 0; le_uint32_t unknown_a4 = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(G_DarkFalzActions_6x19, 0x10); // 6x1A: Invalid subcommand @@ -4048,13 +4051,13 @@ struct G_DarkFalzActions_6x19 { struct G_Unknown_6x1B { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x1B, 4); // 6x1C: Destroy NPC (protected on V3/V4) struct G_DestroyNPC_6x1C { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_DestroyNPC_6x1C, 4); // 6x1D: Invalid subcommand // 6x1E: Invalid subcommand @@ -4063,12 +4066,12 @@ struct G_DestroyNPC_6x1C { struct G_SetPlayerFloor_DCNTE_6x1F { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_SetPlayerFloor_DCNTE_6x1F, 4); struct G_SetPlayerFloor_6x1F { G_ClientIDHeader header; le_int32_t floor = 0; -} __packed__; +} __packed_ws__(G_SetPlayerFloor_6x1F, 8); // 6x20: Set position (protected on V3/V4) // Existing clients send this in response to a 6x1F command when a new client @@ -4081,14 +4084,14 @@ struct G_SetPosition_6x20 { le_float y = 0.0f; le_float z = 0.0f; le_uint32_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_SetPosition_6x20, 0x18); // 6x21: Inter-level warp (protected on V3/V4) struct G_InterLevelWarp_6x21 { G_ClientIDHeader header; le_int32_t floor = 0; -} __packed__; +} __packed_ws__(G_InterLevelWarp_6x21, 8); // 6x22: Set player invisible (protected on V3/V4) // 6x23: Set player visible (protected on V3/V4) @@ -4096,7 +4099,7 @@ struct G_InterLevelWarp_6x21 { struct G_SetPlayerVisibility_6x22_6x23 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_SetPlayerVisibility_6x22_6x23, 4); // 6x24: Teleport player (protected on V3/V4) @@ -4106,7 +4109,7 @@ struct G_TeleportPlayer_6x24 { le_float x = 0.0f; le_float y = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_TeleportPlayer_6x24, 0x14); // 6x25: Equip item (protected on V3/V4) @@ -4115,7 +4118,7 @@ struct G_EquipItem_6x25 { le_uint32_t item_id = 0; // Values here match the EquipSlot enum (in ItemData.hh) le_uint32_t equip_slot = 0; -} __packed__; +} __packed_ws__(G_EquipItem_6x25, 0x0C); // 6x26: Unequip item (protected on V3/V4) @@ -4123,14 +4126,14 @@ struct G_UnequipItem_6x26 { G_ClientIDHeader header; le_uint32_t item_id = 0; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(G_UnequipItem_6x26, 0x0C); // 6x27: Use item (protected on V3/V4) struct G_UseItem_6x27 { G_ClientIDHeader header; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(G_UseItem_6x27, 8); // 6x28: Feed MAG (protected on V3/V4) @@ -4138,7 +4141,7 @@ struct G_FeedMAG_6x28 { G_ClientIDHeader header; le_uint32_t mag_item_id = 0; le_uint32_t fed_item_id = 0; -} __packed__; +} __packed_ws__(G_FeedMAG_6x28, 0x0C); // 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) (protected on V3 but not V4) // This subcommand is also used for reducing the size of stacks - if amount is @@ -4148,7 +4151,7 @@ struct G_DeleteInventoryItem_6x29 { G_ClientIDHeader header; le_uint32_t item_id = 0; le_uint32_t amount = 0; -} __packed__; +} __packed_ws__(G_DeleteInventoryItem_6x29, 0x0C); // 6x2A: Drop item (protected on V3/V4) @@ -4160,7 +4163,7 @@ struct G_DropItem_6x2A { le_float x = 0.0f; le_float y = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_DropItem_6x2A, 0x18); // 6x2B: Create item in inventory (e.g. via tekker or bank withdraw) (protected on V3/V4) // On BB, the 6xBE command is used instead of 6x2B to create inventory items. @@ -4168,13 +4171,13 @@ struct G_DropItem_6x2A { struct G_CreateInventoryItem_DC_6x2B { G_ClientIDHeader header; ItemData item_data; -} __packed__; +} __packed_ws__(G_CreateInventoryItem_DC_6x2B, 0x18); struct G_CreateInventoryItem_PC_V3_BB_6x2B : G_CreateInventoryItem_DC_6x2B { uint8_t unused1 = 0; uint8_t unknown_a2 = 0; parray unused2 = 0; -} __packed__; +} __packed_ws__(G_CreateInventoryItem_PC_V3_BB_6x2B, 0x1C); // 6x2C: Talk to NPC (protected on V3/V4) @@ -4185,13 +4188,13 @@ struct G_TalkToNPC_6x2C { le_float unknown_a3 = 0.0f; le_float unknown_a4 = 0.0f; le_float unknown_a5 = 0.0f; -} __packed__; +} __packed_ws__(G_TalkToNPC_6x2C, 0x14); // 6x2D: Done talking to NPC (protected on V3/V4) struct G_EndTalkToNPC_6x2D { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_EndTalkToNPC_6x2D, 4); // 6x2E: Set and/or clear player flags (protected on V3/V4) @@ -4199,7 +4202,7 @@ struct G_SetOrClearPlayerFlags_6x2E { G_ClientIDHeader header; le_uint32_t and_mask = 0; le_uint32_t or_mask = 0; -} __packed__; +} __packed_ws__(G_SetOrClearPlayerFlags_6x2E, 0x0C); // 6x2F: Change player HP @@ -4208,13 +4211,13 @@ struct G_ChangePlayerHP_6x2F { le_uint32_t type = 0; // 0 = set HP, 1 = add/subtract HP, 2 = add/sub fixed HP le_uint16_t amount = 0; le_uint16_t client_id = 0; -} __packed__; +} __packed_ws__(G_ChangePlayerHP_6x2F, 0x0C); // 6x30: Level up struct G_LevelUp_DCNTE_6x30 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_LevelUp_DCNTE_6x30, 4); struct G_LevelUp_6x30 { G_ClientIDHeader header; @@ -4226,19 +4229,19 @@ struct G_LevelUp_6x30 { le_uint16_t ata = 0; le_uint16_t level = 0; le_uint16_t unknown_a1 = 0; // Must be 0 or 1 -} __packed__; +} __packed_ws__(G_LevelUp_6x30, 0x14); // 6x31: Resurrect player (protected on V3/V4) struct G_UseMedicalCenter_6x31 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_UseMedicalCenter_6x31, 4); // 6x32: Resurrect player (Medical Center) struct G_Unknown_6x32 { G_UnusedHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x32, 4); // 6x33: Resurrect player (with Moon Atomizer) (protected on V3/V4) @@ -4246,7 +4249,7 @@ struct G_RevivePlayer_6x33 { G_ClientIDHeader header; le_uint16_t client_id2 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_RevivePlayer_6x33, 8); // 6x34: Unknown // This subcommand is completely ignored (at least, by PSO GC). @@ -4262,7 +4265,7 @@ struct G_PhotonBlast_6x37 { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_PhotonBlast_6x37, 8); // 6x38: Donate to photon blast (protected on V3/V4) @@ -4270,25 +4273,25 @@ struct G_Unknown_6x38 { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x38, 8); // 6x39: Photon blast ready (protected on V3/V4) struct G_PhotonBlastReady_6x38 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_PhotonBlastReady_6x38, 4); // 6x3A: Unknown (supported; game only) (protected on V3/V4) struct G_Unknown_6x3A { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x3A, 4); // 6x3B: Unknown (supported; lobby & game) (protected on V3/V4) struct G_Unknown_6x3B { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x3B, 4); // 6x3C: Unknown (DCv1 and earlier) // This command has a handler, but it does nothing, even on DC NTE. @@ -4306,7 +4309,7 @@ struct G_StopAtPosition_6x3E { le_float x = 0.0f; le_float y = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_StopAtPosition_6x3E, 0x18); // 6x3F: Set position (protected on V3/V4) @@ -4319,7 +4322,7 @@ struct G_SetPosition_6x3F { le_float x = 0.0f; le_float y = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_SetPosition_6x3F, 0x18); // 6x40: Walk (protected on V3/V4) @@ -4328,7 +4331,7 @@ struct G_WalkToPosition_6x40 { le_float x = 0.0f; le_float z = 0.0f; le_uint32_t action = 0; -} __packed__; +} __packed_ws__(G_WalkToPosition_6x40, 0x10); // 6x41: Unknown // This subcommand is completely ignored by v2 and later. @@ -4337,7 +4340,7 @@ struct G_Unknown_6x41 { G_ClientIDHeader header; le_float x = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_Unknown_6x41, 0x0C); // 6x42: Run (protected on V3/V4) @@ -4345,7 +4348,7 @@ struct G_RunToPosition_6x42 { G_ClientIDHeader header; le_float x = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_RunToPosition_6x42, 0x0C); // 6x43: First attack (protected on V3/V4) // 6x44: Second attack (protected on V3/V4) @@ -4355,21 +4358,21 @@ struct G_Attack_6x43_6x44_6x45 { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_Attack_6x43_6x44_6x45, 8); // 6x46: Attack finished (sent after each of 43, 44, and 45) (protected on V3/V4) struct TargetEntry { le_uint16_t entity_id = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(TargetEntry, 4); struct G_AttackFinished_6x46 { G_ClientIDHeader header; le_uint32_t count = 0; // The client may send a shorter command if not all of these are used. parray targets; -} __packed__; +} __packed_ws__(G_AttackFinished_6x46, 0x30); // 6x47: Cast technique (protected on V3/V4) @@ -4386,7 +4389,7 @@ struct G_CastTechnique_6x47 { uint8_t target_count = 0; // Must be in [0, 10] // The client may send a shorter command if not all of these are used. parray targets; -} __packed__; +} __packed_ws__(G_CastTechnique_6x47, 0x30); // 6x48: Cast technique complete (protected on V3/V4) @@ -4396,7 +4399,7 @@ struct G_CastTechniqueComplete_6x48 { // This level matches the level sent in the 6x47 command, even if that level // was overridden by a preceding 6x8D command. le_uint16_t level = 0; -} __packed__; +} __packed_ws__(G_CastTechniqueComplete_6x48, 8); // 6x49: Subtract photon blast energy (protected on V3/V4) @@ -4410,16 +4413,16 @@ struct G_SubtractPBEnergy_6x49 { struct Entry { le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; - } __packed__; - // The client mauy send a shorter command if not all of these are used. - parray entries; -} __packed__; + } __packed_ws__(Entry, 4); + // The client may send a shorter command if not all of these are used. + parray entries; +} __packed_ws__(G_SubtractPBEnergy_6x49, 0x34); // 6x4A: Fully shield attack (protected on V3/V4) struct G_ShieldAttack_6x4A { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_ShieldAttack_6x4A, 4); // 6x4B: Hit by enemy (protected on V3/V4) // 6x4C: Hit by enemy (protected on V3/V4) @@ -4430,33 +4433,33 @@ struct G_HitByEnemy_6x4B_6x4C { le_uint16_t damage = 0; le_float x_velocity = 0.0f; le_float z_velocity = 0.0f; -} __packed__; +} __packed_ws__(G_HitByEnemy_6x4B_6x4C, 0x10); // 6x4D: Player died (protected on V3/V4) struct G_PlayerDied_6x4D { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_PlayerDied_6x4D, 8); // 6x4E: Player is dead can be revived (protected on V3/V4) struct G_PlayerRevivable_6x4E { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_PlayerRevivable_6x4E, 4); // 6x4F: Player revived (protected on V3/V4) struct G_PlayerRevived_6x4F { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_PlayerRevived_6x4F, 4); // 6x50: Switch interaction (protected on V3/V4) struct G_SwitchInteraction_6x50 { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_SwitchInteraction_6x50, 8); // 6x51: Invalid subcommand @@ -4467,20 +4470,20 @@ struct G_SetAnimationState_6x52 { le_uint16_t animation = 0; le_uint16_t unknown_a2 = 0; le_uint32_t angle = 0; -} __packed__; +} __packed_ws__(G_SetAnimationState_6x52, 0x0C); // 6x53: Unknown (supported; game only) (protected on V3/V4) struct G_Unknown_6x53 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x53, 4); // 6x54: Unknown // This subcommand is completely ignored (at least, by PSO GC). struct G_Unknown_6x54 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x54, 4); // 6x55: Intra-map warp (protected on V3/V4) @@ -4493,7 +4496,7 @@ struct G_IntraMapWarp_6x55 { le_float x2 = 0.0f; le_float y2 = 0.0f; le_float z2 = 0.0f; -} __packed__; +} __packed_ws__(G_IntraMapWarp_6x55, 0x20); // 6x56: Unknown (supported; game) (protected on V3/V4) @@ -4503,13 +4506,13 @@ struct G_Unknown_6x56 { le_float x = 0.0f; le_float y = 0.0f; le_float z = 0.0f; -} __packed__; +} __packed_ws__(G_Unknown_6x56, 0x14); // 6x57: Unknown (supported; lobby & game) (protected on V3/V4) struct G_Unknown_6x57 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x57, 4); // 6x58: Lobby animation (protected on V3/V4) @@ -4517,7 +4520,7 @@ struct G_LobbyAnimation_6x58 { G_ClientIDHeader header; le_uint16_t animation_number = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_LobbyAnimation_6x58, 8); // 6x59: Pick up item @@ -4526,7 +4529,7 @@ struct G_PickUpItem_6x59 { le_uint16_t client_id2 = 0; le_uint16_t floor = 0; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(G_PickUpItem_6x59, 0x0C); // 6x5A: Request to pick up item @@ -4535,7 +4538,7 @@ struct G_PickUpItemRequest_6x5A { le_uint32_t item_id = 0; le_uint16_t floor = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_PickUpItemRequest_6x5A, 0x0C); // 6x5B: Unknown (DCv1 and earlier) // This command has a handler, but it does nothing, even on DC NTE. @@ -4555,18 +4558,18 @@ struct G_DropStackedItem_DC_6x5D { le_float x = 0.0f; le_float z = 0.0f; ItemData item_data; -} __packed__; +} __packed_ws__(G_DropStackedItem_DC_6x5D, 0x24); struct G_DropStackedItem_PC_V3_BB_6x5D : G_DropStackedItem_DC_6x5D { le_uint32_t unused3 = 0; -} __packed__; +} __packed_ws__(G_DropStackedItem_PC_V3_BB_6x5D, 0x28); // 6x5E: Buy item at shop struct G_BuyShopItem_6x5E { G_ClientIDHeader header; ItemData item_data; -} __packed__; +} __packed_ws__(G_BuyShopItem_6x5E, 0x18); // 6x5F: Drop item from box/enemy @@ -4583,16 +4586,16 @@ struct FloorItem { /* 0E */ le_uint16_t drop_number = 0; /* 10 */ ItemData item; /* 24 */ -} __packed__; +} __packed_ws__(FloorItem, 0x24); struct G_DropItem_DC_6x5F { G_UnusedHeader header; FloorItem item; -} __packed__; +} __packed_ws__(G_DropItem_DC_6x5F, 0x28); struct G_DropItem_PC_V3_BB_6x5F : G_DropItem_DC_6x5F { le_uint32_t unused3 = 0; -} __packed__; +} __packed_ws__(G_DropItem_PC_V3_BB_6x5F, 0x2C); // 6x60: Request for item drop (handled by the server on BB) @@ -4606,13 +4609,13 @@ struct G_StandardDropItemRequest_DC_6x60 { /* 10 */ le_uint16_t section = 0; /* 12 */ le_uint16_t ignore_def = 0; /* 14 */ -} __packed__; +} __packed_ws__(G_StandardDropItemRequest_DC_6x60, 0x14); struct G_StandardDropItemRequest_PC_V3_BB_6x60 : G_StandardDropItemRequest_DC_6x60 { /* 14 */ uint8_t effective_area = 0; /* 15 */ parray unused; /* 18 */ -} __packed__; +} __packed_ws__(G_StandardDropItemRequest_PC_V3_BB_6x60, 0x18); // 6x61: Activate MAG effect @@ -4620,7 +4623,7 @@ struct G_ActivateMagEffect_6x61 { G_UnusedHeader header; le_uint32_t mag_item_id = 0; le_uint32_t effect_number = 0; -} __packed__; +} __packed_ws__(G_ActivateMagEffect_6x61, 0x0C); // 6x62: Unknown // This command has a handler, but it does nothing even on DC NTE. @@ -4631,7 +4634,7 @@ struct G_DestroyFloorItem_6x5C_6x63 { G_UnusedHeader header; le_uint32_t item_id = 0; le_uint32_t floor = 0; -} __packed__; +} __packed_ws__(G_DestroyFloorItem_6x5C_6x63, 0x0C); // 6x64: Unknown (not valid on Episode 3) // This command has a handler, but it does nothing even on DC NTE. @@ -4644,7 +4647,7 @@ struct G_DestroyFloorItem_6x5C_6x63 { struct G_UseStarAtomizer_6x66 { G_UnusedHeader header; parray target_client_ids; -} __packed__; +} __packed_ws__(G_UseStarAtomizer_6x66, 0x0C); // 6x67: Trigger set event @@ -4653,7 +4656,7 @@ struct G_TriggerSetEvent_6x67 { le_uint32_t floor = 0; le_uint32_t event_id = 0; // NOT event index le_uint32_t client_id = 0; -} __packed__; +} __packed_ws__(G_TriggerSetEvent_6x67, 0x10); // 6x68: Create telepipe / cast Ryuker @@ -4666,7 +4669,7 @@ struct G_CreateTelepipe_6x68 { le_float y = 0.0f; le_float z = 0.0f; le_uint32_t unused3 = 0; -} __packed__; +} __packed_ws__(G_CreateTelepipe_6x68, 0x1C); // 6x69: NPC control // Note: NPCs cannot be destroyed with 6x69; 6x1C is used instead for that. @@ -4677,7 +4680,7 @@ struct G_NPCControl_6x69 { le_uint16_t param2; // Commands 0/3: npc_entity_id; commands 1/2: unused le_uint16_t command = 0; // 0 = create follower NPC, 1 = stop acting, 2 = start acting, 3 = create attacker NPC le_uint16_t param3; // Commands 0/3: npc_template_index; commands 1/2: unused -} __packed__; +} __packed_ws__(G_NPCControl_6x69, 0x0C); // 6x6A: Use boss warp (not valid on Episode 3) @@ -4685,22 +4688,22 @@ struct G_UseBossWarp_6x6A { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_UseBossWarp_6x6A, 8); // 6x6B: Sync enemy state (used while loading into game) struct G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E { - G_ExtendedHeader header; + G_ExtendedHeaderT header; le_uint32_t decompressed_size = 0; // BC0-compressed data follows here (see bc0_decompress) -} __packed__; +} __packed_ws__(G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E, 0x0C); struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E { - G_ExtendedHeader header; + G_ExtendedHeaderT header; le_uint32_t decompressed_size = 0; le_uint32_t compressed_size = 0; // Must be <= subcommand_size - 0x10 // BC0-compressed data follows here (see bc0_decompress) -} __packed__; +} __packed_ws__(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E, 0x10); // Decompressed format is a list of these struct G_SyncEnemyState_6x6B_Entry_Decompressed { @@ -4711,7 +4714,7 @@ struct G_SyncEnemyState_6x6B_Entry_Decompressed { uint8_t red_buff_level = 0; uint8_t blue_buff_type = 0; uint8_t blue_buff_level = 0; -} __packed__; +} __packed_ws__(G_SyncEnemyState_6x6B_Entry_Decompressed, 0x0C); // 6x6C: Sync object state (used while loading into game) // Compressed format is the same as 6x6B. @@ -4720,7 +4723,7 @@ struct G_SyncEnemyState_6x6B_Entry_Decompressed { struct G_SyncObjectState_6x6C_Entry_Decompressed { le_uint16_t flags = 0; le_uint16_t item_drop_id = 0; -} __packed__; +} __packed_ws__(G_SyncObjectState_6x6C_Entry_Decompressed, 4); // 6x6D: Sync item state (used while loading into game) // Internal name: RcvItemCondition @@ -4754,14 +4757,14 @@ struct G_SyncItemState_6x6D_Decompressed { // (floor 0) isn't included in next_drop_number_per_floor (so Forest 1 is [0] // in that array) but it is included in floor_item_count_per_floor (so Forest // 1 is [1] there). - parray next_drop_number_per_floor; + /* 00 */ parray next_drop_number_per_floor; // Only [0]-[3] in this array are ever actually used in normal gameplay, but // the client fills in all 12 of these with reasonable values. - parray next_item_id_per_player; - parray floor_item_count_per_floor; + /* 20 */ parray next_item_id_per_player; + /* 50 */ parray floor_item_count_per_floor; // Variable-length field: - // FloorItem items[sum(floor_item_count_per_floor)]; -} __packed__; + /* 8C */ // FloorItem items[sum(floor_item_count_per_floor)]; +} __packed_ws__(G_SyncItemState_6x6D_Decompressed, 0x8C); // 6x6E: Sync set flag state (used while loading into game) // Compressed format is the same as 6x6B. @@ -4784,20 +4787,20 @@ struct G_SyncSetFlagState_6x6E_Decompressed { // Variable-length fields follow here: // le_uint16_t object_set_flags[num_object_sets]; // le_uint16_t enemy_set_flags[num_enemy_sets]; - } __packed__; -} __packed__; + } __packed_ws__(EntitySetFlags, 0x10); +} __packed_ws__(G_SyncSetFlagState_6x6E_Decompressed, 8); // 6x6F: Set quest flags (used while loading into game) struct G_SetQuestFlags_DCv1_6x6F { G_UnusedHeader header; QuestFlagsV1 quest_flags; -} __packed__; +} __packed_ws__(G_SetQuestFlags_DCv1_6x6F, 0x184); struct G_SetQuestFlags_V2_V3_6x6F { G_UnusedHeader header; QuestFlags quest_flags; -} __packed__; +} __packed_ws__(G_SetQuestFlags_V2_V3_6x6F, 0x204); struct G_SetQuestFlags_BB_6x6F { G_UnusedHeader header; @@ -4806,7 +4809,7 @@ struct G_SetQuestFlags_BB_6x6F { // (in PlayerSubordinates.cc) are overwritten on the receiving client's end. // The client always sends this with use_apply_mask = 1. le_uint32_t use_apply_mask = 1; -} __packed__; +} __packed_ws__(G_SetQuestFlags_BB_6x6F, 0x208); // 6x70: Sync player disp data and inventory (used while loading into game) // Annoyingly, they didn't use the same format as the 65/67/68 commands here, @@ -4822,8 +4825,7 @@ struct Telepipe { /* 10 */ le_float z = 0.0f; /* 14 */ le_uint32_t unknown_a3 = 0; /* 18 */ le_uint32_t unknown_a4 = 0x0000FFFF; - /* 1C */ -} __packed__; +} __packed_ws__(Telepipe, 0x1C); struct G_Unknown_6x70_SubA1 { // This is used in all versions of this command except DCNTE and 11/2000. @@ -4833,16 +4835,14 @@ struct G_Unknown_6x70_SubA1 { /* 08 */ le_float unknown_a4 = 0.0f; /* 0C */ le_uint32_t unknown_a5 = 0; /* 10 */ le_uint32_t unknown_a6 = 0; - /* 14 */ -} __packed__; +} __packed_ws__(G_Unknown_6x70_SubA1, 0x14); struct G_Unknown_6x70_SubA2 { // This is used in all versions of this command except DCNTE and 11/2000. /* 00 */ le_uint32_t unknown_a1 = 0; /* 04 */ le_float unknown_a2 = 0.0f; /* 08 */ le_uint32_t unknown_a3 = 0; - /* 0C */ -} __packed__; +} __packed_ws__(G_Unknown_6x70_SubA2, 0x0C); struct G_SyncPlayerDispAndInventory_BaseDCNTE { /* 0000 */ le_uint16_t client_id = 0; @@ -4856,11 +4856,11 @@ struct G_SyncPlayerDispAndInventory_BaseDCNTE { /* 001C */ le_uint32_t angle_z = 0; /* 0020 */ le_uint16_t unknown_a3a = 0; /* 0022 */ le_uint16_t current_hp = 0; -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_BaseDCNTE, 0x24); struct G_SyncPlayerDispAndInventory_DCNTE_6x70 { // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeader header = {{0x60, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DCNTE_6x70)}; + /* 0004 */ G_ExtendedHeaderT header = {{0x60, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DCNTE_6x70)}; /* 000C */ G_SyncPlayerDispAndInventory_BaseDCNTE base; // The following two fields appear to contain uninitialized data /* 0030 */ le_uint32_t unknown_a5 = 0; @@ -4875,11 +4875,11 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 { /* 00E4 */ le_uint32_t num_items = 0; /* 00E8 */ parray items; /* 0430 */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_DCNTE_6x70, 0x42C); struct G_SyncPlayerDispAndInventory_DC112000_6x70 { // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeader header = {{0x67, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC112000_6x70)}; + /* 0004 */ G_ExtendedHeaderT header = {{0x67, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC112000_6x70)}; /* 000C */ G_SyncPlayerDispAndInventory_BaseDCNTE base; /* 0030 */ le_uint16_t bonus_hp_from_materials = 0; /* 0032 */ le_uint16_t bonus_tp_from_materials = 0; @@ -4894,7 +4894,7 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 { /* 00F0 */ le_uint32_t num_items = 0; /* 00F4 */ parray items; /* 043C */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_DC112000_6x70, 0x438); struct G_SyncPlayerDispAndInventory_BaseV1 { /* 0000 */ G_SyncPlayerDispAndInventory_BaseDCNTE base; @@ -4914,33 +4914,33 @@ struct G_SyncPlayerDispAndInventory_BaseV1 { /* 00B4 */ parray technique_levels_v1 = 0xFF; // Last byte is uninitialized /* 00C8 */ PlayerVisualConfig visual; /* 0118 */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_BaseV1, 0x118); struct G_SyncPlayerDispAndInventory_DC_PC_6x70 { // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeader header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)}; + /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)}; /* 000C */ G_SyncPlayerDispAndInventory_BaseV1 base; /* 0124 */ PlayerStats stats; /* 0148 */ le_uint32_t num_items = 0; /* 014C */ parray items; /* 0494 */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_DC_PC_6x70, 0x490); // GC NTE also uses this format. struct G_SyncPlayerDispAndInventory_GC_6x70 { // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeader header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)}; + /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)}; /* 000C */ G_SyncPlayerDispAndInventory_BaseV1 base; /* 0124 */ PlayerStats stats; /* 0148 */ le_uint32_t num_items = 0; /* 014C */ parray items; /* 0494 */ le_uint32_t floor = 0; /* 0498 */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_GC_6x70, 0x494); struct G_SyncPlayerDispAndInventory_XB_6x70 { // Offsets in this struct are relative to the overall command header - /* 0004 */ G_ExtendedHeader header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)}; + /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)}; /* 000C */ G_SyncPlayerDispAndInventory_BaseV1 base; /* 0124 */ PlayerStats stats; /* 0148 */ le_uint32_t num_items = 0; @@ -4950,11 +4950,11 @@ struct G_SyncPlayerDispAndInventory_XB_6x70 { /* 049C */ le_uint32_t xb_user_id_low = 0; /* 04A0 */ le_uint32_t unknown_a16 = 0; /* 04A4 */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_XB_6x70, 0x4A0); struct G_SyncPlayerDispAndInventory_BB_6x70 { // Offsets in this struct are relative to the overall command header - /* 0008 */ G_ExtendedHeader header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; + /* 0008 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; /* 0010 */ G_SyncPlayerDispAndInventory_BaseV1 base; /* 0128 */ pstring name; /* 0148 */ PlayerStats stats; @@ -4965,19 +4965,19 @@ struct G_SyncPlayerDispAndInventory_BB_6x70 { /* 04C0 */ le_uint32_t xb_user_id_low = 0; /* 04C4 */ le_uint32_t unknown_a16 = 0; /* 04C8 */ -} __packed__; +} __packed_ws__(G_SyncPlayerDispAndInventory_BB_6x70, 0x4C0); // 6x71: Unknown (used while loading into game) struct G_Unknown_6x71 { G_UnusedHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x71, 4); // 6x72: Player done loading into game struct G_DoneLoadingIntoGame_6x72 { G_UnusedHeader header; -} __packed__; +} __packed_ws__(G_DoneLoadingIntoGame_6x72, 4); // 6x73: Exit quest // This command misbehaves if sent in a lobby or in a game when no quest is @@ -4985,28 +4985,24 @@ struct G_DoneLoadingIntoGame_6x72 { struct G_ExitQuest_6x73 { G_UnusedHeader header; -} __packed__; +} __packed_ws__(G_ExitQuest_6x73, 4); // 6x74: Word select // There is a bug in PSO GC with regard to this command: the client does not // byteswap the header, which means the client_id field is big-endian. -struct WordSelectMessage { - le_uint16_t num_tokens = 0; - le_uint16_t target_type = 0; - parray tokens; - le_uint32_t numeric_parameter = 0; - le_uint32_t unknown_a4 = 0; -} __packed__; - template -struct G_WordSelect_6x74 { +struct G_WordSelectT_6x74 { using U16T = typename std::conditional::type; uint8_t subcommand = 0; uint8_t size = 0; U16T client_id = 0; WordSelectMessage message; } __packed__; +using G_WordSelect_6x74 = G_WordSelectT_6x74; +using G_WordSelectBE_6x74 = G_WordSelectT_6x74; +check_struct_size(G_WordSelect_6x74, 0x20); +check_struct_size(G_WordSelectBE_6x74, 0x20); // 6x75: Update quest flag @@ -5014,12 +5010,12 @@ struct G_UpdateQuestFlag_DC_PC_6x75 { G_UnusedHeader header; le_uint16_t flag = 0; // Must be < 0x400 le_uint16_t action = 0; // 0 = set flag, 1 = clear flag -} __packed__; +} __packed_ws__(G_UpdateQuestFlag_DC_PC_6x75, 8); struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 { le_uint16_t difficulty = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_UpdateQuestFlag_V3_BB_6x75, 0x0C); // 6x76: Set entity set flags // This command can only be used to set set flags, since the game performs a @@ -5029,7 +5025,7 @@ struct G_SetEntitySetFlags_6x76 { G_EnemyIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object le_uint16_t floor = 0; le_uint16_t flags = 0; -} __packed__; +} __packed_ws__(G_SetEntitySetFlags_6x76, 8); // 6x77: Sync quest data // This is sent by the client when an opcode D9 is executed within a quest. @@ -5042,7 +5038,7 @@ struct G_SyncQuestData_6x77 { le_uint32_t as_int; le_float as_float; } __packed__ value; -} __packed__; +} __packed_ws__(G_SyncQuestData_6x77, 0x0C); // 6x78: Unknown @@ -5051,7 +5047,7 @@ struct G_Unknown_6x78 { le_uint16_t client_id = 0; // Must be < 12 le_uint16_t unused1 = 0; le_uint32_t unused2 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x78, 0x0C); // 6x79: Lobby 14/15 gogo ball (soccer game) @@ -5063,19 +5059,19 @@ struct G_GogoBall_6x79 { le_float unknown_a4 = 0.0f; uint8_t unknown_a5 = 0; parray unused; -} __packed__; +} __packed_ws__(G_GogoBall_6x79, 0x18); // 6x7A: Unknown (protected on V3/V4) struct G_Unknown_6x7A { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x7A, 4); // 6x7B: Unknown (protected on V3/V4) struct G_Unknown_6x7B { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_Unknown_6x7B, 4); // 6x7C: Set Challenge records (not valid on Episode 3) @@ -5083,20 +5079,20 @@ struct G_SetChallengeRecordsBase_6x7C { G_UnusedHeader header; le_uint16_t client_id = 0; parray unknown_a1; -} __packed__; +} __packed_ws__(G_SetChallengeRecordsBase_6x7C, 8); struct G_SetChallengeRecords_DC_6x7C : G_SetChallengeRecordsBase_6x7C { - PlayerRecordsDC_Challenge records; -} __packed__; + PlayerRecordsChallengeDC records; +} __packed_ws__(G_SetChallengeRecords_DC_6x7C, 0xA8); struct G_SetChallengeRecords_PC_6x7C : G_SetChallengeRecordsBase_6x7C { - PlayerRecordsPC_Challenge records; -} __packed__; + PlayerRecordsChallengePC records; +} __packed_ws__(G_SetChallengeRecords_PC_6x7C, 0xE0); struct G_SetChallengeRecords_V3_6x7C : G_SetChallengeRecordsBase_6x7C { - PlayerRecordsV3_Challenge records; -} __packed__; + PlayerRecordsChallengeV3 records; +} __packed_ws__(G_SetChallengeRecords_V3_6x7C, 0x108); struct G_SetChallengeRecords_BB_6x7C : G_SetChallengeRecordsBase_6x7C { - PlayerRecordsBB_Challenge records; -} __packed__; + PlayerRecordsChallengeBB records; +} __packed_ws__(G_SetChallengeRecords_BB_6x7C, 0x148); // 6x7D: Set battle mode data (not valid on Episode 3) @@ -5115,7 +5111,7 @@ struct G_SetBattleModeData_6x7D { uint8_t unused = 0; uint8_t is_alive = 0; // Only used when what == 3 parray params; -} __packed__; +} __packed_ws__(G_SetBattleModeData_6x7D, 0x18); // 6x7E: Unknown (not valid on Episode 3) // This subcommand is completely ignored (at least, by PSO GC). @@ -5123,17 +5119,21 @@ struct G_SetBattleModeData_6x7D { // 6x7F: Battle scores and places (not valid on Episode 3) template -struct G_BattleScores_6x7F { +struct G_BattleScoresT_6x7F { using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; struct Entry { U16T client_id = 0; U16T place = 0; U32T score = 0; - } __packed__; + } __packed_ws__(Entry, 8); G_UnusedHeader header; parray entries; } __packed__; +using G_BattleScores_6x7F = G_BattleScoresT_6x7F; +using G_BattleScoresBE_6x7F = G_BattleScoresT_6x7F; +check_struct_size(G_BattleScores_6x7F, 0x24); +check_struct_size(G_BattleScoresBE_6x7F, 0x24); // 6x80: Trigger trap (not valid on Episode 3) @@ -5141,19 +5141,19 @@ struct G_TriggerTrap_6x80 { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_TriggerTrap_6x80, 8); // 6x81: Set drop weapon on death flag (protected on V3/V4) struct G_SetDropWeaponOnDeathFlag_6x81 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_SetDropWeaponOnDeathFlag_6x81, 4); // 6x82: Clear drop weapon on death flag (protected on V3/V4) struct G_ClearDropWeaponOnDeathFlag_6x82 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_ClearDropWeaponOnDeathFlag_6x82, 4); // 6x83: Place trap (protected on V3/V4) @@ -5161,7 +5161,7 @@ struct G_PlaceTrap_6x83 { G_ClientIDHeader header; le_uint16_t trap_type = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_PlaceTrap_6x83, 8); // 6x84: Vol Opt boss actions (not valid on Episode 3) // Same format and usage as 6x16, except unknown_a2 is ignored in 6x84. @@ -5172,7 +5172,7 @@ struct G_VolOptBossActions_6x84 { le_uint16_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_VolOptBossActions_6x84, 0x10); // 6x85: Unknown (supported; game only; not valid on Episode 3) @@ -5180,7 +5180,7 @@ struct G_Unknown_6x85 { G_UnusedHeader header; le_uint16_t unknown_a1 = 0; // Command is ignored unless this is 0 parray unknown_a2; // Only the first 3 appear to be used -} __packed__; +} __packed_ws__(G_Unknown_6x85, 0x14); // 6x86: Hit destructible object (not valid on Episode 3) @@ -5190,20 +5190,20 @@ struct G_HitDestructibleObject_6x86 { le_uint32_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; le_uint16_t unknown_a4 = 0; -} __packed__; +} __packed_ws__(G_HitDestructibleObject_6x86, 0x10); // 6x87: Shrink player (protected on V3/V4) struct G_ShrinkPlayer_6x87 { G_ClientIDHeader header; le_float unknown_a1 = 0.0f; -} __packed__; +} __packed_ws__(G_ShrinkPlayer_6x87, 8); // 6x88: Restore shrunken player (protected on V3/V4) struct G_RestoreShrunkenPlayer_6x88 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_RestoreShrunkenPlayer_6x88, 4); // 6x89: Player killed by monster (protected on V3/V4) @@ -5211,14 +5211,14 @@ struct G_PlayerKilledByMonster_6x89 { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unused = 0; -} __packed__; +} __packed_ws__(G_PlayerKilledByMonster_6x89, 8); // 6x8A: Unknown (not valid on Episode 3) struct G_Unknown_6x8A { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; // Must be < 0x11 -} __packed__; +} __packed_ws__(G_Unknown_6x8A, 8); // 6x8B: Unknown (not valid on Episode 3) // This command has a handler, but it does nothing. @@ -5235,7 +5235,7 @@ struct G_SetTechniqueLevelOverride_6x8D { uint8_t level_upgrade = 0; uint8_t unused1 = 0; le_uint16_t unused2 = 0; -} __packed__; +} __packed_ws__(G_SetTechniqueLevelOverride_6x8D, 8); // 6x8E: Unknown (not valid on Episode 3) // This command has a handler, but it does nothing. @@ -5246,14 +5246,14 @@ struct G_Unknown_6x8F { G_ClientIDHeader header; le_uint16_t client_id2 = 0; le_uint16_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x8F, 8); // 6x90: Unknown (not valid on Episode 3) (protected on V3/V4) struct G_Unknown_6x90 { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x90, 8); // 6x91: Unknown (supported; game only) @@ -5266,7 +5266,7 @@ struct G_Unknown_6x91 { le_uint16_t switch_flag_num = 0; uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared uint8_t switch_flag_floor = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x91, 0x14); // 6x92: Unknown (not valid on Episode 3) @@ -5274,7 +5274,7 @@ struct G_Unknown_6x92 { G_UnusedHeader header; le_uint32_t unknown_a1 = 0; le_float unknown_a2 = 0.0f; -} __packed__; +} __packed_ws__(G_Unknown_6x92, 0x0C); // 6x93: Activate timed switch (not valid on Episode 3) @@ -5284,7 +5284,7 @@ struct G_ActivateTimedSwitch_6x93 { le_uint16_t switch_flag_num = 0; uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared parray unused; -} __packed__; +} __packed_ws__(G_ActivateTimedSwitch_6x93, 0x0C); // 6x94: Warp (not valid on Episode 3) @@ -5292,17 +5292,17 @@ struct G_InterLevelWarp_6x94 { G_UnusedHeader header; le_uint16_t floor = 0; parray unused; -} __packed__; +} __packed_ws__(G_InterLevelWarp_6x94, 8); // 6x95: Unknown (not valid on Episode 3) struct G_Unknown_6x95 { G_UnusedHeader header; le_uint32_t client_id = 0; - ChallengeTime challenge_time = 0; + ChallengeTime challenge_time; le_uint32_t unused1 = 0; le_uint32_t unused2 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x95, 0x14); // 6x96: Unknown (not valid on Episode 3) // This command has a handler, but it does nothing. @@ -5315,7 +5315,7 @@ struct G_SelectChallengeModeFailureOption_6x97 { le_uint32_t is_retry = 0; le_uint32_t unused2 = 0; le_uint32_t unused3 = 0; -} __packed__; +} __packed_ws__(G_SelectChallengeModeFailureOption_6x97, 0x14); // 6x98: Unknown // This subcommand is completely ignored (at least, by PSO GC). @@ -5336,7 +5336,7 @@ struct G_UpdatePlayerStat_6x9A { // 4 = add TP uint8_t what = 0; uint8_t amount = 0; -} __packed__; +} __packed_ws__(G_UpdatePlayerStat_6x9A, 8); // 6x9B: Unknown (protected on V3/V4) @@ -5344,21 +5344,21 @@ struct G_Unknown_6x9B { G_UnusedHeader header; uint8_t unknown_a1 = 0; parray unused; -} __packed__; +} __packed_ws__(G_Unknown_6x9B, 8); // 6x9C: Unknown (supported; game only; not valid on Episode 3) struct G_Unknown_6x9C { G_EnemyIDHeader header; le_uint32_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x9C, 8); // 6x9D: Unknown (not valid on Episode 3) struct G_Unknown_6x9D { G_UnusedHeader header; le_uint32_t client_id2 = 0; -} __packed__; +} __packed_ws__(G_Unknown_6x9D, 8); // 6x9E: Play camera shutter sound // This subcommand is only used on PSO PC and PC NTE. It is not implemented (and @@ -5367,7 +5367,7 @@ struct G_Unknown_6x9D { struct G_PlayerCameraShutterSound_6x9E { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_PlayerCameraShutterSound_6x9E, 4); // 6x9F: Gal Gryphon boss actions (not valid on pre-V3 or Episode 3) @@ -5376,7 +5376,7 @@ struct G_GalGryphonBossActions_6x9F { le_uint32_t unknown_a1 = 0; le_float unknown_a2 = 0.0f; le_float unknown_a3 = 0.0f; -} __packed__; +} __packed_ws__(G_GalGryphonBossActions_6x9F, 0x10); // 6xA0: Gal Gryphon boss actions (not valid on pre-V3 or Episode 3) @@ -5389,13 +5389,13 @@ struct G_GalGryphonBossActions_6xA0 { le_uint16_t unknown_a2 = 0; le_uint16_t unknown_a3 = 0; parray unknown_a4; -} __packed__; +} __packed_ws__(G_GalGryphonBossActions_6xA0, 0x28); // 6xA1: Revive player (not valid on pre-V3) (protected on V3/V4) struct G_RevivePlayer_V3_BB_6xA1 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_RevivePlayer_V3_BB_6xA1, 4); // 6xA2: Specializable item drop request (not valid on pre-V3; handled by // server on BB) @@ -5406,7 +5406,7 @@ struct G_SpecializableItemDropRequest_6xA2 : G_StandardDropItemRequest_PC_V3_BB_ /* 20 */ le_uint32_t param5 = 0; /* 24 */ le_uint32_t param6 = 0; /* 28 */ -} __packed__; +} __packed_ws__(G_SpecializableItemDropRequest_6xA2, 0x28); // 6xA3: Olga Flow boss actions (not valid on pre-V3 or Episode 3) @@ -5415,7 +5415,7 @@ struct G_OlgaFlowBossActions_6xA3 { uint8_t unknown_a1 = 0; uint8_t unknown_a2 = 0; parray unknown_a3; -} __packed__; +} __packed_ws__(G_OlgaFlowBossActions_6xA3, 8); // 6xA4: Olga Flow phase 1 boss actions (not valid on pre-V3 or Episode 3) @@ -5423,7 +5423,7 @@ struct G_OlgaFlowPhase1BossActions_6xA4 { G_EnemyIDHeader header; uint8_t what = 0; parray unknown_a3; -} __packed__; +} __packed_ws__(G_OlgaFlowPhase1BossActions_6xA4, 8); // 6xA5: Olga Flow phase 2 boss actions (not valid on pre-V3 or Episode 3) @@ -5431,7 +5431,7 @@ struct G_OlgaFlowPhase2BossActions_6xA5 { G_EnemyIDHeader header; uint8_t what = 0; parray unknown_a3; -} __packed__; +} __packed_ws__(G_OlgaFlowPhase2BossActions_6xA5, 8); // 6xA6: Modify trade proposal (not valid on pre-V3) @@ -5442,7 +5442,7 @@ struct G_ModifyTradeProposal_6xA6 { parray unknown_a3; le_uint32_t unknown_a4 = 0; le_uint32_t unknown_a5 = 0; -} __packed__; +} __packed_ws__(G_ModifyTradeProposal_6xA6, 0x10); // 6xA7: Unknown (not valid on pre-V3) // This subcommand is completely ignored. @@ -5450,7 +5450,7 @@ struct G_ModifyTradeProposal_6xA6 { // 6xA8: Gol Dragon boss actions (not valid on pre-V3 or Episode 3) template -struct G_GolDragonBossActions_6xA8 { +struct G_GolDragonBossActionsT_6xA8 { using F32T = typename std::conditional::type; G_EnemyIDHeader header; le_uint16_t unknown_a2 = 0; @@ -5461,11 +5461,10 @@ struct G_GolDragonBossActions_6xA8 { uint8_t unknown_a5 = 0; parray unused; } __packed__; - -struct G_GolDragonBossActions_XB_BB_6xA8 : G_GolDragonBossActions_6xA8 { -} __packed__; -struct G_GolDragonBossActions_GC_6xA8 : G_GolDragonBossActions_6xA8 { -} __packed__; +using G_GolDragonBossActions_XB_BB_6xA8 = G_GolDragonBossActionsT_6xA8; +using G_GolDragonBossActions_GC_6xA8 = G_GolDragonBossActionsT_6xA8; +check_struct_size(G_GolDragonBossActions_XB_BB_6xA8, 0x18); +check_struct_size(G_GolDragonBossActions_GC_6xA8, 0x18); // 6xA9: Barba Ray boss actions (not valid on pre-V3 or Episode 3) @@ -5473,7 +5472,7 @@ struct G_BarbaRayBossActions_6xA9 { G_EnemyIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_BarbaRayBossActions_6xA9, 8); // 6xAA: Barba Ray boss actions (not valid on pre-V3 or Episode 3) @@ -5482,7 +5481,7 @@ struct G_BarbaRayBossActions_6xAA { le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; le_uint32_t unknown_a3 = 0; -} __packed__; +} __packed_ws__(G_BarbaRayBossActions_6xAA, 0x0C); // 6xAB: Create lobby chair (not valid on pre-V3) (protected on V3/V4) // This command's appears to be different on GC NTE than on any other version. @@ -5492,13 +5491,13 @@ struct G_Unknown_GCNTE_6xAB { G_EnemyIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_Unknown_GCNTE_6xAB, 8); struct G_CreateLobbyChair_6xAB { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_CreateLobbyChair_6xAB, 8); // 6xAC: Unknown (not valid on pre-V3) (protected on V3/V4) // This command's appears to be different on GC NTE than on any other version. @@ -5510,13 +5509,13 @@ struct G_Unknown_GCNTE_6xAC { le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; le_uint32_t unknown_a3 = 0; -} __packed__; +} __packed_ws__(G_Unknown_GCNTE_6xAC, 0x0C); struct G_Unknown_6xAC { G_ClientIDHeader header; le_uint32_t num_items = 0; parray item_ids; -} __packed__; +} __packed_ws__(G_Unknown_6xAC, 0x80); // 6xAD: Olga Flow subordinate boss actions (not valid on pre-V3, Episode 3, or // GC Trial Edition) @@ -5525,7 +5524,7 @@ struct G_OlgaFlowSubordinateBossActions_6xAD { G_UnusedHeader header; // The first byte in this array seems to have a special meaning parray unknown_a1; -} __packed__; +} __packed_ws__(G_OlgaFlowSubordinateBossActions_6xAD, 0x44); // 6xAE: Set lobby chair state (sent by existing clients at join time) // This subcommand is not valid on DC, PC, or GC Trial Edition. @@ -5536,21 +5535,21 @@ struct G_SetLobbyChairState_6xAE { le_uint16_t unknown_a2 = 0; le_uint32_t unknown_a3 = 0; le_uint32_t unknown_a4 = 0; -} __packed__; +} __packed_ws__(G_SetLobbyChairState_6xAE, 0x10); // 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) struct G_TurnLobbyChair_6xAF { G_ClientIDHeader header; le_uint32_t angle = 0; // In range [0x0000, 0xFFFF] -} __packed__; +} __packed_ws__(G_TurnLobbyChair_6xAF, 8); // 6xB0: Move lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) struct G_MoveLobbyChair_6xB0 { G_ClientIDHeader header; le_uint32_t unknown_a1 = 0; -} __packed__; +} __packed_ws__(G_MoveLobbyChair_6xB0, 8); // 6xB1: Unknown (not valid on pre-V3 or GC Trial Edition) // This subcommand is completely ignored. @@ -5565,7 +5564,7 @@ struct G_PlaySoundFromPlayer_6xB2 { uint8_t unused = 0; le_uint16_t client_id = 0; le_uint32_t sound_id = 0; // 0x00051720 = camera shutter sound -} __packed__; +} __packed_ws__(G_PlaySoundFromPlayer_6xB2, 0x0C); // 6xB3: Unknown (Xbox; voice chat) @@ -5573,7 +5572,7 @@ struct G_Unknown_XB_6xB3 { G_ClientIDHeader header; le_uint32_t num_frames; // (0x0A * num_frames) bytes of data follows here. -} __packed__; +} __packed_ws__(G_Unknown_XB_6xB3, 8); // 6xB3: CARD battle server data request (Episode 3) @@ -5597,7 +5596,7 @@ struct G_CardBattleCommandHeader { // masking and may send uninitialized data in this field. uint8_t mask_key = 0x00; uint8_t unused2 = 0x00; -} __packed__; +} __packed_ws__(G_CardBattleCommandHeader, 8); // Unlike all other 6x subcommands, the 6xB3 subcommand is sent to the server in // a CA command instead of a 6x, C9, or CB command. (For this reason, we @@ -5619,14 +5618,14 @@ struct G_CardServerDataCommandHeader { /* 08 */ be_uint32_t sequence_num = 0; /* 0C */ be_uint32_t context_token = 0; /* 10 */ -} __packed__; +} __packed_ws__(G_CardServerDataCommandHeader, 0x10); // 6xB4: Unknown (Xbox; voice chat) struct G_Unknown_XB_6xB4 { G_ClientIDHeader header; le_uint32_t unknown_a1; -} __packed__; +} __packed_ws__(G_Unknown_XB_6xB4, 8); // 6xB4: CARD battle server response (Episode 3) - see 6xB3 above // 6xB5: CARD battle client command (Episode 3) - see 6xB3 above @@ -5636,7 +5635,7 @@ struct G_Unknown_XB_6xB4 { struct G_ShopContentsRequest_BB_6xB5 { G_UnusedHeader header; le_uint32_t shop_type = 0; -} __packed__; +} __packed_ws__(G_ShopContentsRequest_BB_6xB5, 8); // 6xB6: Episode 3 map list and map contents (server->client only) // Unlike 6xB3-6xB5, these commands cannot be masked. Also unlike 6xB3-6xB5, @@ -5647,10 +5646,10 @@ struct G_ShopContentsRequest_BB_6xB5 { // separate subcommand from the other CARD battle subcommands.) struct G_MapSubsubcommand_Ep3_6xB6 { - G_ExtendedHeader header; + G_ExtendedHeaderT header; uint8_t subsubcommand = 0; // 0x40 or 0x41 parray unused; -} __packed__; +} __packed_ws__(G_MapSubsubcommand_Ep3_6xB6, 0x0C); struct G_MapList_Ep3_6xB6x40 { G_MapSubsubcommand_Ep3_6xB6 header; @@ -5659,7 +5658,7 @@ struct G_MapList_Ep3_6xB6x40 { // PRS-compressed map list data follows here. newserv generates this from the // map index when requested; see the MapList struct in Episode3/DataIndexes.hh // and Episode3::MapIndex::get_compressed_map_list for details on the format. -} __packed__; +} __packed_ws__(G_MapList_Ep3_6xB6x40, 0x10); struct G_MapData_Ep3_6xB6x41 { G_MapSubsubcommand_Ep3_6xB6 header; @@ -5668,7 +5667,7 @@ struct G_MapData_Ep3_6xB6x41 { le_uint16_t unused = 0; // PRS-compressed map data follows here (which decompresses to an // Episode3::MapDefinition). -} __packed__; +} __packed_ws__(G_MapData_Ep3_6xB6x41, 0x14); // 6xB6: BB shop contents (server->client only) @@ -5679,7 +5678,7 @@ struct G_ShopContents_BB_6xB6 { le_uint16_t unused = 0; // Note: data2d of these entries should be the price parray item_datas; -} __packed__; +} __packed_ws__(G_ShopContents_BB_6xB6, 0x198); // 6xB7: Alias for 6xB3 (Episode 3 Trial Edition) // This command behaves exactly the same as 6xB3. This alias exists only in @@ -5694,7 +5693,7 @@ struct G_BuyShopItem_BB_6xB7 { uint8_t item_index = 0; uint8_t amount = 0; uint8_t unknown_a1 = 0; // TODO: Probably actually unused; verify this -} __packed__; +} __packed_ws__(G_BuyShopItem_BB_6xB7, 0x0C); // 6xB8: Alias for 6xB4 (Episode 3 Trial Edition) // This command behaves exactly the same as 6xB4. This alias exists only in @@ -5705,7 +5704,7 @@ struct G_BuyShopItem_BB_6xB7 { struct G_IdentifyItemRequest_6xB8 { G_UnusedHeader header; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(G_IdentifyItemRequest_6xB8, 8); // 6xB9: Alias for 6xB5 (Episode 3 Trial Edition) // This command behaves exactly the same as 6xB5. This alias exists only in @@ -5716,7 +5715,7 @@ struct G_IdentifyItemRequest_6xB8 { struct G_IdentifyResult_BB_6xB9 { G_ClientIDHeader header; ItemData item_data; -} __packed__; +} __packed_ws__(G_IdentifyResult_BB_6xB9, 0x18); // 6xBA: Sync card trade state (Episode 3) // This command calls various member functions in TCardTradeServer. @@ -5727,14 +5726,14 @@ struct G_SyncCardTradeState_Ep3_6xBA { le_uint16_t unknown_a2 = 0; le_uint32_t unknown_a3 = 0; le_uint32_t unknown_a4 = 0; -} __packed__; +} __packed_ws__(G_SyncCardTradeState_Ep3_6xBA, 0x10); // 6xBA: BB accept tekker result (handled by the server) struct G_AcceptItemIdentification_BB_6xBA { G_UnusedHeader header; le_uint32_t item_id = 0; -} __packed__; +} __packed_ws__(G_AcceptItemIdentification_BB_6xBA, 8); // 6xBB: Sync card trade state (Episode 3) // This command calls various member functions in TCardTradeServer. @@ -5747,7 +5746,7 @@ struct G_SyncCardTradeState_Ep3_6xBB { le_uint16_t what = 0; // Must be < 5; this indexes into a jump table le_uint16_t slot = 0; parray args; -} __packed__; +} __packed_ws__(G_SyncCardTradeState_Ep3_6xBB, 0x18); // 6xBB: BB bank request (handled by the server) @@ -5765,17 +5764,17 @@ struct G_CardCounts_Ep3_6xBC { parray unknown_a1; // The client sends uninitialized data in this field parray unused; -} __packed__; +} __packed_ws__(G_CardCounts_Ep3_6xBC, 0x2FC); // 6xBC: BB bank contents (server->client only) struct G_BankContentsHeader_BB_6xBC { - G_ExtendedHeader header; + G_ExtendedHeaderT header; le_uint32_t checksum = 0; // can be random; client won't notice le_uint32_t num_items = 0; le_uint32_t meseta = 0; // Item data follows -} __packed__; +} __packed_ws__(G_BankContentsHeader_BB_6xBC, 0x14); // 6xBD: Word select during battle (Episode 3; not Trial Edition) @@ -5792,7 +5791,7 @@ struct G_WordSelectDuringBattle_Ep3_6xBD { // message when sent during an Episode 3 battle. uint8_t private_flags = 0; parray unused; -} __packed__; +} __packed_ws__(G_WordSelectDuringBattle_Ep3_6xBD, 0x24); // 6xBD: BB bank action (take/deposit meseta/item) (handled by the server) @@ -5803,7 +5802,7 @@ struct G_BankAction_BB_6xBD { uint8_t action = 0; // 0 = deposit, 1 = take, 3 = done (close bank window) uint8_t item_amount = 0; le_uint16_t item_index = 0; // 0xFFFF = meseta -} __packed__; +} __packed_ws__(G_BankAction_BB_6xBD, 0x10); // 6xBE: Sound chat (Episode 3; not Trial Edition) // This is the only subcommand ever sent with the CB command. @@ -5812,7 +5811,7 @@ struct G_SoundChat_Ep3_6xBE { G_UnusedHeader header; le_uint32_t sound_id = 0; // Must be < 0x27 be_uint32_t unused = 0; -} __packed__; +} __packed_ws__(G_SoundChat_Ep3_6xBE, 0x0C); // 6xBE: BB create inventory item (server->client only) @@ -5820,21 +5819,21 @@ struct G_CreateInventoryItem_BB_6xBE { G_ClientIDHeader header; ItemData item_data; le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(G_CreateInventoryItem_BB_6xBE, 0x1C); // 6xBF: Change lobby music (Episode 3; not Trial Edition) struct G_ChangeLobbyMusic_Ep3_6xBF { G_UnusedHeader header; le_uint32_t song_number = 0; // Must be < 0x34 -} __packed__; +} __packed_ws__(G_ChangeLobbyMusic_Ep3_6xBF, 8); // 6xBF: Give EXP (BB) (server->client only) struct G_GiveExperience_BB_6xBF { G_ClientIDHeader header; le_uint32_t amount = 0; -} __packed__; +} __packed_ws__(G_GiveExperience_BB_6xBF, 8); // 6xC0: Sell item at shop (BB) (protected on V3/V4) @@ -5842,7 +5841,7 @@ struct G_SellItemAtShop_BB_6xC0 { G_UnusedHeader header; le_uint32_t item_id = 0; le_uint32_t amount = 0; -} __packed__; +} __packed_ws__(G_SellItemAtShop_BB_6xC0, 0x0C); // 6xC1: Invite to team (BB) // 6xC2: Accept invitation to team (BB) @@ -5852,7 +5851,7 @@ struct G_TeamInvitationAction_BB_6xC1_6xC2_6xCD_6xCE { le_uint32_t guild_card_number = 0; le_uint32_t action = 0; // 0 or 1 for 6xC1, 2 (or not 2) for 6xC2 parray unknown_a1; -} __packed__; +} __packed_ws__(G_TeamInvitationAction_BB_6xC1_6xC2_6xCD_6xCE, 0x60); // 6xC3: Split stacked item (BB; handled by the server) // Note: This is not sent if an entire stack is dropped; in that case, a normal @@ -5866,20 +5865,20 @@ struct G_SplitStackedItem_BB_6xC3 { le_float z = 0.0f; le_uint32_t item_id = 0; le_uint32_t amount = 0; -} __packed__; +} __packed_ws__(G_SplitStackedItem_BB_6xC3, 0x18); // 6xC4: Sort inventory (BB; handled by the server) struct G_SortInventory_BB_6xC4 { G_UnusedHeader header; parray item_ids; -} __packed__; +} __packed_ws__(G_SortInventory_BB_6xC4, 0x7C); // 6xC5: Medical center used (BB) struct G_MedicalCenterUsed_BB_6xC5 { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_MedicalCenterUsed_BB_6xC5, 4); // 6xC6: Steal experience (BB) @@ -5887,7 +5886,7 @@ struct G_StealEXP_BB_6xC6 { G_ClientIDHeader header; le_uint16_t entity_id = 0; le_uint16_t enemy_index = 0; -} __packed__; +} __packed_ws__(G_StealEXP_BB_6xC6, 8); // 6xC7: Charge attack (BB) @@ -5896,7 +5895,7 @@ struct G_ChargeAttack_BB_6xC7 { // Tethealla (at least, the ancient public version of it) treats this as // signed, and gives the player money in that case. We don't do so. le_uint32_t meseta_amount = 0; -} __packed__; +} __packed_ws__(G_ChargeAttack_BB_6xC7, 8); // 6xC8: Enemy EXP request (BB; handled by the server) @@ -5906,21 +5905,21 @@ struct G_EnemyEXPRequest_BB_6xC8 { le_uint16_t requesting_client_id = 0; uint8_t is_killer = 0; parray unused; -} __packed__; +} __packed_ws__(G_EnemyEXPRequest_BB_6xC8, 0x0C); // 6xC9: Adjust player Meseta (BB; handled by server) struct G_AdjustPlayerMeseta_BB_6xC9 { G_UnusedHeader header; le_int32_t amount = 0; -} __packed__; +} __packed_ws__(G_AdjustPlayerMeseta_BB_6xC9, 8); // 6xCA: Request item reward from quest (BB; handled by server) struct G_ItemRewardRequest_BB_6xCA { G_UnusedHeader header; ItemData item_data; -} __packed__; +} __packed_ws__(G_ItemRewardRequest_BB_6xCA, 0x18); // 6xCB: Transfer item via mail message (BB) @@ -5929,7 +5928,7 @@ struct G_TransferItemViaMailMessage_BB_6xCB { le_uint32_t item_id = 0; le_uint32_t amount = 0; le_uint32_t target_guild_card_number = 0; -} __packed__; +} __packed_ws__(G_TransferItemViaMailMessage_BB_6xCB, 0x10); // 6xCC: Exchange item for team points (BB) (protected on V3/V4) @@ -5937,7 +5936,7 @@ struct G_ExchangeItemForTeamPoints_BB_6xCC { G_ClientIDHeader header; le_uint32_t item_id = 0; le_uint32_t amount = 0; -} __packed__; +} __packed_ws__(G_ExchangeItemForTeamPoints_BB_6xCC, 0x0C); // 6xCD: Transfer master (BB) // 6xCE: Accept master transfer (BB) @@ -5948,7 +5947,7 @@ struct G_ExchangeItemForTeamPoints_BB_6xCC { struct G_StartBattle_BB_6xCF { G_UnusedHeader header; BattleRules rules; -} __packed__; +} __packed_ws__(G_StartBattle_BB_6xCF, 0x34); // 6xD0: Battle mode level up (BB; handled by server) // Requests the client to be leveled up by num_levels levels. The server should @@ -5957,7 +5956,7 @@ struct G_StartBattle_BB_6xCF { struct G_BattleModeLevelUp_BB_6xD0 { G_ClientIDHeader header; le_uint32_t num_levels = 0; -} __packed__; +} __packed_ws__(G_BattleModeLevelUp_BB_6xD0, 8); // 6xD1: Request Challenge Mode grave recovery item (BB; handled by server) @@ -5968,7 +5967,7 @@ struct G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1 { le_float x = 0; le_float z = 0; le_uint32_t item_type = 0; // Should be < 6 -} __packed__; +} __packed_ws__(G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1, 0x14); // 6xD2: Set quest counter (BB) // Writes 4 bytes to the 32-bit field specified by index. @@ -5977,7 +5976,7 @@ struct G_SetQuestCounter_BB_6xD2 { G_ClientIDHeader header; le_uint32_t index = 0; // There are 0x10 of them (0x00-0x0F) le_uint32_t value = 0; -} __packed__; +} __packed_ws__(G_SetQuestCounter_BB_6xD2, 0x0C); // 6xD3: Invalid subcommand @@ -5988,7 +5987,7 @@ struct G_Unknown_BB_6xD4 { le_uint16_t action = 0; // Must be in [0, 5] uint8_t unknown_a1 = 0; // Must be in [0, 15] uint8_t unused = 0; -} __packed__; +} __packed_ws__(G_Unknown_BB_6xD4, 8); // 6xD5: Exchange item in quest (BB; handled by server) // The client sends this when it executes an F953 quest opcode. @@ -5999,7 +5998,7 @@ struct G_ExchangeItemInQuest_BB_6xD5 { ItemData replace_item; // Only data1[0]-[2] are used le_uint16_t success_function_id = 0; le_uint16_t failure_function_id = 0; -} __packed__; +} __packed_ws__(G_ExchangeItemInQuest_BB_6xD5, 0x30); // 6xD6: Wrap item (BB; handled by server) @@ -6008,7 +6007,7 @@ struct G_WrapItem_BB_6xD6 { ItemData item; uint8_t unknown_a1 = 0; parray unused; -} __packed__; +} __packed_ws__(G_WrapItem_BB_6xD6, 0x1C); // 6xD7: Paganini Photon Drop exchange (BB; handled by server) // The client sends this when it executes an F955 quest opcode. @@ -6018,7 +6017,7 @@ struct G_PaganiniPhotonDropExchange_BB_6xD7 { ItemData new_item; // Only data1[0]-[2] are used le_uint16_t success_function_id = 0; le_uint16_t failure_function_id = 0; -} __packed__; +} __packed_ws__(G_PaganiniPhotonDropExchange_BB_6xD7, 0x1C); // 6xD8: Add S-rank weapon special (BB; handled by server) // The client sends this when it executes an F956 quest opcode. @@ -6030,7 +6029,7 @@ struct G_AddSRankWeaponSpecial_BB_6xD8 { le_uint32_t special_type = 0; le_uint16_t success_function_id = 0; le_uint16_t failure_function_id = 0; -} __packed__; +} __packed_ws__(G_AddSRankWeaponSpecial_BB_6xD8, 0x24); // 6xD9: Momoka item exchange (BB; handled by server) // The client sends this when it executes an F95B quest opcode. @@ -6043,7 +6042,7 @@ struct G_MomokaItemExchange_BB_6xD9 { le_uint32_t unknown_a4 = 0; le_uint16_t unknown_a5 = 0; le_uint16_t unknown_a6 = 0; -} __packed__; +} __packed_ws__(G_MomokaItemExchange_BB_6xD9, 0x38); // 6xDA: Upgrade weapon attribute (BB; handled by server) // The client sends this when it executes an F957 or F958 quest opcode. @@ -6057,7 +6056,7 @@ struct G_UpgradeWeaponAttribute_BB_6xDA { le_uint32_t payment_type = 0; // 0 = Photon Drops, 1 = Photon Spheres le_uint16_t success_function_id = 0; // argsA[6] le_uint16_t failure_function_id = 0; // argsA[7] -} __packed__; +} __packed_ws__(G_UpgradeWeaponAttribute_BB_6xDA, 0x2C); // 6xDB: Exchange item in quest (BB) @@ -6066,7 +6065,7 @@ struct G_ExchangeItemInQuest_BB_6xDB { le_uint32_t unknown_a1 = 0; le_uint32_t item_id = 0; le_uint32_t amount = 0; -} __packed__; +} __packed_ws__(G_ExchangeItemInQuest_BB_6xDB, 0x10); // 6xDC: Saint-Million boss actions (BB) @@ -6074,7 +6073,7 @@ struct G_SaintMillionBossActions_BB_6xDC { G_UnusedHeader header; le_uint16_t unknown_a1 = 0; le_uint16_t unknown_a2 = 0; -} __packed__; +} __packed_ws__(G_SaintMillionBossActions_BB_6xDC, 8); // 6xDD: Set EXP multiplier (BB) // header.param specifies the EXP multiplier. It is 1-based, so the value 2 @@ -6082,7 +6081,7 @@ struct G_SaintMillionBossActions_BB_6xDC { struct G_SetEXPMultiplier_BB_6xDD { G_ParameterHeader header; -} __packed__; +} __packed_ws__(G_SetEXPMultiplier_BB_6xDD, 4); // 6xDE: Exchange Secret Lottery Ticket (BB; handled by server) // The client sends this when it executes an F95C quest opcode. @@ -6092,14 +6091,14 @@ struct G_ExchangeSecretLotteryTicket_BB_6xDE { uint8_t index = 0; uint8_t function_id1 = 0; le_uint16_t function_id2 = 0; -} __packed__; +} __packed_ws__(G_ExchangeSecretLotteryTicket_BB_6xDE, 8); // 6xDF: Exchange Photon Crystals (BB; handled by server) // The client sends this when it executes an F95D quest opcode. struct G_ExchangePhotonCrystals_BB_6xDF { G_ClientIDHeader header; -} __packed__; +} __packed_ws__(G_ExchangePhotonCrystals_BB_6xDF, 4); // 6xE0: Request item drop from quest (BB; handled by server) // The client sends this when it executes an F95E quest opcode. @@ -6112,7 +6111,7 @@ struct G_RequestItemDropFromQuest_BB_6xE0 { uint8_t unused = 0; le_float x = 0.0f; // argsA[1] le_float z = 0.0f; // argsA[2] -} __packed__; +} __packed_ws__(G_RequestItemDropFromQuest_BB_6xE0, 0x10); // 6xE1: Exchange Photon Tickets (BB; handled by server) // The client sends this when it executes an F95F quest opcode. @@ -6125,7 +6124,7 @@ struct G_ExchangePhotonTickets_BB_6xE1 { uint8_t unused = 0; le_uint16_t function_id1 = 0; // argsA[3] le_uint16_t unknown_a5 = 0; // argsA[4] -} __packed__; +} __packed_ws__(G_ExchangePhotonTickets_BB_6xE1, 0x0C); // 6xE2: Get Meseta slot prize (BB) // The client sends this when it executes an F960 quest opcode. @@ -6138,7 +6137,7 @@ struct G_GetMesetaSlotPrize_BB_6xE2 { uint8_t unused; le_float x; // TODO: Verify this guess le_float z; // TODO: Verify this guess -} __packed__; +} __packed_ws__(G_GetMesetaSlotPrize_BB_6xE2, 0x10); // 6xE3: Set Meseta slot prize result (BB) // The client only uses this to populate the quest text @@ -6147,7 +6146,7 @@ struct G_GetMesetaSlotPrize_BB_6xE2 { struct G_SetMesetaSlotPrizeResult_BB_6xE3 { G_ClientIDHeader header; ItemData item; -} __packed__; +} __packed_ws__(G_SetMesetaSlotPrizeResult_BB_6xE3, 0x18); // 6xE4: Invalid subcommand // 6xE5: Invalid subcommand @@ -6206,7 +6205,7 @@ struct G_UpdateHand_Ep3_6xB4x02 { /* 0A */ le_uint16_t unused = 0; /* 0C */ Episode3::HandAndEquipState state; /* 60 */ -} __packed__; +} __packed_ws__(G_UpdateHand_Ep3_6xB4x02, 0x60); // 6xB4x03: Set state flags @@ -6214,7 +6213,7 @@ struct G_SetStateFlags_Ep3_6xB4x03 { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetStateFlags_Ep3_6xB4x03) / 4, 0, 0x03, 0, 0, 0}; /* 08 */ Episode3::StateFlags state; /* 20 */ -} __packed__; +} __packed_ws__(G_SetStateFlags_Ep3_6xB4x03, 0x20); // 6xB4x04: Update SC/FC short statuses @@ -6229,7 +6228,7 @@ struct G_UpdateShortStatuses_Ep3_6xB4x04 { // [15] is the set assist card /* 000C */ parray card_statuses; /* 010C */ -} __packed__; +} __packed_ws__(G_UpdateShortStatuses_Ep3_6xB4x04, 0x10C); // 6xB4x05: Update map state // TODO: This structure is different on Ep3 NTE because the Rules structure is @@ -6241,7 +6240,7 @@ struct G_UpdateMap_Ep3NTE_6xB4x05 { /* 0138 */ uint8_t start_battle = 0; /* 0139 */ parray unused; /* 013C */ -} __packed__; +} __packed_ws__(G_UpdateMap_Ep3NTE_6xB4x05, 0x13C); struct G_UpdateMap_Ep3_6xB4x05 { /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateMap_Ep3_6xB4x05) / 4, 0, 0x05, 0, 0, 0}; @@ -6249,7 +6248,7 @@ struct G_UpdateMap_Ep3_6xB4x05 { /* 0140 */ uint8_t start_battle = 0; /* 0141 */ parray unused; /* 0144 */ -} __packed__; +} __packed_ws__(G_UpdateMap_Ep3_6xB4x05, 0x144); // 6xB4x06: Apply condition effect @@ -6257,7 +6256,7 @@ struct G_ApplyConditionEffect_Ep3_6xB4x06 { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_ApplyConditionEffect_Ep3_6xB4x06) / 4, 0, 0x06, 0, 0, 0}; /* 08 */ Episode3::EffectResult effect; /* 14 */ -} __packed__; +} __packed_ws__(G_ApplyConditionEffect_Ep3_6xB4x06, 0x14); // 6xB4x07: Set battle decks @@ -6266,7 +6265,7 @@ struct G_UpdateDecks_Ep3_6xB4x07 { /* 0008 */ parray entries_present; /* 000C */ parray entries; /* 016C */ -} __packed__; +} __packed_ws__(G_UpdateDecks_Ep3_6xB4x07, 0x16C); // 6xB4x09: Set action state @@ -6276,7 +6275,7 @@ struct G_SetActionState_Ep3_6xB4x09 { /* 0A */ parray unknown_a1; /* 0C */ Episode3::ActionState state; /* 70 */ -} __packed__; +} __packed_ws__(G_SetActionState_Ep3_6xB4x09, 0x70); // 6xB4x0A: Update action chain and metadata // This command is used by Trial Edition. The final version sends 6xB4x4C, @@ -6293,7 +6292,7 @@ struct G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A { /* 000C */ Episode3::ActionChainWithCondsTrial chain; /* 010C */ Episode3::ActionMetadata metadata; /* 0180 */ -} __packed__; +} __packed_ws__(G_UpdateActionChainAndMetadata_Ep3NTE_6xB4x0A, 0x180); struct G_UpdateActionChainAndMetadata_Ep3_6xB4x0A { /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateActionChainAndMetadata_Ep3_6xB4x0A) / 4, 0, 0x0A, 0, 0, 0}; @@ -6303,7 +6302,7 @@ struct G_UpdateActionChainAndMetadata_Ep3_6xB4x0A { /* 000C */ Episode3::ActionChainWithConds chain; /* 010C */ Episode3::ActionMetadata metadata; /* 0180 */ -} __packed__; +} __packed_ws__(G_UpdateActionChainAndMetadata_Ep3_6xB4x0A, 0x180); // 6xB3x0B / CAx0B: Redraw initial hand (immediately before battle) @@ -6311,7 +6310,7 @@ struct G_RedrawInitialHand_Ep3_CAx0B { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_RedrawInitialHand_Ep3_CAx0B) / 4, 0, 0x0B, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; -} __packed__; +} __packed_ws__(G_RedrawInitialHand_Ep3_CAx0B, 0x14); // 6xB3x0C / CAx0C: End initial redraw phase @@ -6319,7 +6318,7 @@ struct G_EndInitialRedrawPhase_Ep3_CAx0C { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndInitialRedrawPhase_Ep3_CAx0C) / 4, 0, 0x0C, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; -} __packed__; +} __packed_ws__(G_EndInitialRedrawPhase_Ep3_CAx0C, 0x14); // 6xB3x0D / CAx0D: End non-action phase // This command is sent when the client has no more actions to take during the @@ -6331,7 +6330,7 @@ struct G_EndNonAttackPhase_Ep3_CAx0D { le_uint16_t client_id = 0; le_uint16_t battle_phase = 0; // Only used on NTE parray unused2; -} __packed__; +} __packed_ws__(G_EndNonAttackPhase_Ep3_CAx0D, 0x1C); // 6xB3x0E / CAx0E: Discard card from hand @@ -6339,7 +6338,7 @@ struct G_DiscardCardFromHand_Ep3_CAx0E { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_DiscardCardFromHand_Ep3_CAx0E) / 4, 0, 0x0E, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; le_uint16_t card_ref = 0xFFFF; -} __packed__; +} __packed_ws__(G_DiscardCardFromHand_Ep3_CAx0E, 0x14); // 6xB3x0F / CAx0F: Set card from hand @@ -6350,7 +6349,7 @@ struct G_SetCardFromHand_Ep3_CAx0F { le_uint16_t set_index = 0; le_uint16_t assist_target_player = 0; Episode3::Location loc; -} __packed__; +} __packed_ws__(G_SetCardFromHand_Ep3_CAx0F, 0x1C); // 6xB3x10 / CAx10: Move field character @@ -6359,7 +6358,7 @@ struct G_MoveFieldCharacter_Ep3_CAx10 { le_uint16_t client_id = 0; le_uint16_t set_index = 0; Episode3::Location loc; -} __packed__; +} __packed_ws__(G_MoveFieldCharacter_Ep3_CAx10, 0x18); // 6xB3x11 / CAx11: Enqueue action (play card(s) during action phase) // This command is used for playing both attacks (and the associated action @@ -6372,7 +6371,7 @@ struct G_EnqueueAttackOrDefense_Ep3_CAx11 { le_uint16_t client_id = 0; parray unused2; Episode3::ActionState entry; -} __packed__; +} __packed_ws__(G_EnqueueAttackOrDefense_Ep3_CAx11, 0x78); // 6xB3x12 / CAx12: End attack list (done playing cards during action phase) // This command informs the server that the client is done playing attacks in @@ -6382,7 +6381,7 @@ struct G_EndAttackList_Ep3_CAx12 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndAttackList_Ep3_CAx12) / 4, 0, 0x12, 0, 0, 0, 0, 0}; le_uint16_t client_id = 0; parray unused2; -} __packed__; +} __packed_ws__(G_EndAttackList_Ep3_CAx12, 0x14); // 6xB3x13 / CAx13: Set map state during setup @@ -6390,13 +6389,13 @@ struct G_SetMapState_Ep3NTE_CAx13 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_Ep3NTE_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; Episode3::MapAndRulesStateTrial map_and_rules_state; Episode3::OverlayState overlay_state; -} __packed__; +} __packed_ws__(G_SetMapState_Ep3NTE_CAx13, 0x2B4); struct G_SetMapState_Ep3_CAx13 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetMapState_Ep3_CAx13) / 4, 0, 0x13, 0, 0, 0, 0, 0}; Episode3::MapAndRulesState map_and_rules_state; Episode3::OverlayState overlay_state; -} __packed__; +} __packed_ws__(G_SetMapState_Ep3_CAx13, 0x2BC); // 6xB3x14 / CAx14: Set player deck during setup @@ -6407,7 +6406,7 @@ struct G_SetPlayerDeck_Ep3_CAx14 { /* 13 */ uint8_t unused2 = 0; /* 14 */ Episode3::DeckEntry entry; /* 6C */ -} __packed__; +} __packed_ws__(G_SetPlayerDeck_Ep3_CAx14, 0x6C); // 6xB3x15 / CAx15: Hard-reset server state // This command appears to be completely unused; the client never sends it. @@ -6415,7 +6414,7 @@ struct G_SetPlayerDeck_Ep3_CAx14 { struct G_HardResetServerState_Ep3_CAx15 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_HardResetServerState_Ep3_CAx15) / 4, 0, 0x15, 0, 0, 0, 0, 0}; // No arguments -} __packed__; +} __packed_ws__(G_HardResetServerState_Ep3_CAx15, 0x10); // 6xB5x17: Unknown // TODO: Document this from Episode 3 client/server disassembly @@ -6423,7 +6422,7 @@ struct G_HardResetServerState_Ep3_CAx15 { struct G_Unknown_Ep3_6xB5x17 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x17) / 4, 0, 0x17, 0, 0, 0}; // No arguments -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB5x17, 8); // 6xB5x1A: Force disconnect // This command seems to cause the client to unconditionally disconnect. The @@ -6435,7 +6434,7 @@ struct G_Unknown_Ep3_6xB5x17 { struct G_ForceDisconnect_Ep3_6xB5x1A { G_CardBattleCommandHeader header = {0xB5, sizeof(G_ForceDisconnect_Ep3_6xB5x1A) / 4, 0, 0x1A, 0, 0, 0}; // No arguments -} __packed__; +} __packed_ws__(G_ForceDisconnect_Ep3_6xB5x1A, 8); // 6xB3x1B / CAx1B: Set player name during setup // Curiously, this command can be used during a non-setup phase; the server @@ -6444,14 +6443,14 @@ struct G_ForceDisconnect_Ep3_6xB5x1A { struct G_SetPlayerName_Ep3_CAx1B { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_SetPlayerName_Ep3_CAx1B) / 4, 0, 0x1B, 0, 0, 0, 0, 0}; Episode3::NameEntry entry; -} __packed__; +} __packed_ws__(G_SetPlayerName_Ep3_CAx1B, 0x24); // 6xB4x1C: Set all player names struct G_SetPlayerNames_Ep3_6xB4x1C { G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetPlayerNames_Ep3_6xB4x1C) / 4, 0, 0x1C, 0, 0, 0}; parray entries; -} __packed__; +} __packed_ws__(G_SetPlayerNames_Ep3_6xB4x1C, 0x58); // 6xB3x1D / CAx1D: Request for battle start // The battle actually begins when the server sends a state flags update (in @@ -6460,7 +6459,7 @@ struct G_SetPlayerNames_Ep3_6xB4x1C { struct G_StartBattle_Ep3_CAx1D { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_StartBattle_Ep3_CAx1D) / 4, 0, 0x1D, 0, 0, 0, 0, 0}; -} __packed__; +} __packed_ws__(G_StartBattle_Ep3_CAx1D, 0x10); // 6xB4x1E: Action result @@ -6471,7 +6470,7 @@ struct G_ActionResult_Ep3_6xB4x1E { /* 0D */ uint8_t response_phase = 0; /* 0E */ parray unused; /* 10 */ -} __packed__; +} __packed_ws__(G_ActionResult_Ep3_6xB4x1E, 0x10); // 6xB4x1F: Set context token // This token is sent back in the context_token field of all CA commands from @@ -6482,7 +6481,7 @@ struct G_SetContextToken_Ep3_6xB4x1F { // Note that this field is little-endian, but the corresponding context_token // field in G_CardServerDataCommandHeader is big-endian! le_uint32_t context_token = 0; -} __packed__; +} __packed_ws__(G_SetContextToken_Ep3_6xB4x1F, 0x0C); // 6xB5x20: Unknown // TODO: Document this from Episode 3 client/server disassembly @@ -6493,14 +6492,14 @@ struct G_Unknown_Ep3_6xB5x20 { le_uint32_t guild_card_number = 0; uint8_t client_id = 0; parray unused; -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB5x20, 0x14); // 6xB3x21 / CAx21: End battle struct G_EndBattle_Ep3_CAx21 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndBattle_Ep3_CAx21) / 4, 0, 0x21, 0, 0, 0, 0, 0}; le_uint32_t unused2 = 0; -} __packed__; +} __packed_ws__(G_EndBattle_Ep3_CAx21, 0x14); // 6xB4x22: Unknown // This command appears to be completely unused. The client's handler for this @@ -6510,7 +6509,7 @@ struct G_EndBattle_Ep3_CAx21 { struct G_Unknown_Ep3_6xB4x22 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x22) / 4, 0, 0x22, 0, 0, 0}; // No arguments -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB4x22, 8); // 6xB4x23: Unknown // This command was actually sent by Sega's original servers, but it does @@ -6521,7 +6520,7 @@ struct G_Unknown_Ep3_6xB4x23 { uint8_t present = 0; // Handler expects this to be equal to 1 uint8_t client_id = 0; parray unused; -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB4x23, 0x0C); // 6xB5x27: Unknown // TODO: Document this from Episode 3 client/server disassembly @@ -6534,7 +6533,7 @@ struct G_Unknown_Ep3_6xB5x27 { le_uint32_t unknown_a2 = 0; // Must be < 0x10 le_uint32_t unknown_a3 = 0; le_uint32_t unused = 0; // Curiously, this usually contains a memory address -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB5x27, 0x18); // 6xB3x28 / CAx28: End defense list // This command informs the server that the client is done playing defense @@ -6545,7 +6544,7 @@ struct G_EndDefenseList_Ep3_CAx28 { uint8_t unused1 = 0; uint8_t client_id = 0; parray unused2; -} __packed__; +} __packed_ws__(G_EndDefenseList_Ep3_CAx28, 0x14); // 6xB4x29: Set action state // TODO: How is this different from 6xB4x09? It looks like the server never @@ -6557,7 +6556,7 @@ struct G_SetActionState_Ep3_6xB4x29 { /* 09 */ parray unknown_a2; /* 0C */ Episode3::ActionState state; /* 70 */ -} __packed__; +} __packed_ws__(G_SetActionState_Ep3_6xB4x29, 0x70); // 6xB4x2A: Unknown // TODO: Document this from Episode 3 client/server disassembly @@ -6567,7 +6566,7 @@ struct G_Unknown_Ep3_6xB4x2A { parray unknown_a1; le_uint16_t unknown_a2 = 0; parray unused; -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB4x2A, 0x10); // 6xB3x2B / CAx2B: Legacy set card // It seems Sega's servers completely ignored this command. The command name is @@ -6577,7 +6576,7 @@ struct G_ExecLegacyCard_Ep3_CAx2B { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_ExecLegacyCard_Ep3_CAx2B) / 4, 0, 0x2B, 0, 0, 0, 0, 0}; le_uint16_t unused2 = 0; parray unused3; -} __packed__; +} __packed_ws__(G_ExecLegacyCard_Ep3_CAx2B, 0x14); // 6xB4x2C: Unknown @@ -6589,7 +6588,7 @@ struct G_Unknown_Ep3_6xB4x2C { /* 10 */ Episode3::Location loc; /* 14 */ parray unknown_a2; /* 1C */ -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB4x2C, 0x1C); // 6xB5x2D: Unknown // TODO: Document this from Episode 3 client/server disassembly @@ -6602,7 +6601,7 @@ struct G_Unknown_Ep3_6xB5x2D { // TODO: Figure out if tournament fast loading can be implemented using this // to fix the stuck-in-wall glitch. parray unknown_a1; -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB5x2D, 0x0C); // 6xB5x2E: Notify other players that battle is about to end @@ -6610,24 +6609,23 @@ struct G_BattleEndNotification_Ep3_6xB5x2E { G_CardBattleCommandHeader header = {0xB5, sizeof(G_BattleEndNotification_Ep3_6xB5x2E) / 4, 0, 0x2E, 0, 0, 0}; uint8_t unknown_a1 = 0; // Command ignored unless this is 0 or 1 parray unused; -} __packed__; +} __packed_ws__(G_BattleEndNotification_Ep3_6xB5x2E, 0x0C); // 6xB5x2F: Set deck in battle setup menu struct G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F { G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F) / 4, 0, 0x2F, 0, 0, 0}; parray unknown_a1; - parray unknown_a2; pstring deck_name; parray unknown_a3; le_uint16_t unknown_a4 = 0; - parray card_ids; + parray card_ids; parray unused; le_uint32_t unknown_a5 = 0; le_uint16_t unknown_a6 = 0; le_uint16_t unknown_a7 = 0; -} __packed__; +} __packed_ws__(G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F, 0x8C); // 6xB5x30: Unknown // The client never sends this command, and when the client received this @@ -6636,7 +6634,7 @@ struct G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F { struct G_Unknown_Ep3_6xB5x30 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x30) / 4, 0, 0x30, 0, 0, 0}; // No arguments -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB5x30, 8); // 6xB5x31: Confirm deck selection @@ -6649,7 +6647,7 @@ struct G_ConfirmDeckSelection_Ep3_6xB5x31 { uint8_t unknown_a4 = 0; // Must be < 0x14 uint8_t unknown_a5 = 0; // Used as an array index parray unused; -} __packed__; +} __packed_ws__(G_ConfirmDeckSelection_Ep3_6xB5x31, 0x10); // 6xB5x32: Move shared menu cursor @@ -6663,7 +6661,7 @@ struct G_MoveSharedMenuCursor_Ep3_6xB5x32 { uint8_t unknown_a4 = 0; uint8_t unknown_a5 = 0; parray unused; -} __packed__; +} __packed_ws__(G_MoveSharedMenuCursor_Ep3_6xB5x32, 0x14); // 6xB4x33: Subtract ally ATK points (e.g. for photon blast) @@ -6673,7 +6671,7 @@ struct G_SubtractAllyATKPoints_Ep3_6xB4x33 { /* 09 */ uint8_t ally_cost = 0; /* 0A */ le_uint16_t card_ref = 0xFFFF; /* 0C */ -} __packed__; +} __packed_ws__(G_SubtractAllyATKPoints_Ep3_6xB4x33, 0x0C); // 6xB3x34 / CAx34: Photon blast request @@ -6682,7 +6680,7 @@ struct G_PhotonBlastRequest_Ep3_CAx34 { uint8_t ally_client_id = 0; uint8_t reason = 0; le_uint16_t card_ref = 0xFFFF; -} __packed__; +} __packed_ws__(G_PhotonBlastRequest_Ep3_CAx34, 0x14); // 6xB4x35: Update photon blast status @@ -6692,7 +6690,7 @@ struct G_PhotonBlastStatus_Ep3_6xB4x35 { /* 09 */ uint8_t accepted = 0; /* 0A */ le_uint16_t card_ref = 0xFFFF; /* 0C */ -} __packed__; +} __packed_ws__(G_PhotonBlastStatus_Ep3_6xB4x35, 0x0C); // 6xB5x36: Recreate player // Setting client_id to a value 4 or greater while in a game causes the player @@ -6705,7 +6703,7 @@ struct G_RecreatePlayer_Ep3_6xB5x36 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_RecreatePlayer_Ep3_6xB5x36) / 4, 0, 0x36, 0, 0, 0}; uint8_t client_id = 0; parray unused; -} __packed__; +} __packed_ws__(G_RecreatePlayer_Ep3_6xB5x36, 0x0C); // 6xB3x37 / CAx37: Ready to advance from starting rolls phase @@ -6713,7 +6711,7 @@ struct G_AdvanceFromStartingRollsPhase_Ep3_CAx37 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_AdvanceFromStartingRollsPhase_Ep3_CAx37) / 4, 0, 0x37, 0, 0, 0, 0, 0}; uint8_t client_id = 0; parray unused2; -} __packed__; +} __packed_ws__(G_AdvanceFromStartingRollsPhase_Ep3_CAx37, 0x14); // 6xB5x38: Card counts request // This command causes the client identified by requested_client_id to send a @@ -6726,7 +6724,7 @@ struct G_CardCountsRequest_Ep3_6xB5x38 { uint8_t requested_client_id = 0; uint8_t reply_to_client_id = 0; parray unused; -} __packed__; +} __packed_ws__(G_CardCountsRequest_Ep3_6xB5x38, 0x0C); // 6xB4x39: Update all player statistics @@ -6734,13 +6732,13 @@ struct G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39 { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39) / 4, 0, 0x39, 0, 0, 0}; /* 08 */ parray stats; /* 58 */ -} __packed__; +} __packed_ws__(G_UpdateAllPlayerStatistics_Ep3NTE_6xB4x39, 0x58); struct G_UpdateAllPlayerStatistics_Ep3_6xB4x39 { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateAllPlayerStatistics_Ep3_6xB4x39) / 4, 0, 0x39, 0, 0, 0}; /* 08 */ parray stats; /* A8 */ -} __packed__; +} __packed_ws__(G_UpdateAllPlayerStatistics_Ep3_6xB4x39, 0xA8); // 6xB3x3A / CAx3A: Overall time limit expired // It seems Sega's servers completely ignored this command and used server-side @@ -6748,7 +6746,7 @@ struct G_UpdateAllPlayerStatistics_Ep3_6xB4x39 { struct G_OverallTimeLimitExpired_Ep3_CAx3A { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_OverallTimeLimitExpired_Ep3_CAx3A) / 4, 0, 0x3A, 0, 0, 0, 0, 0}; -} __packed__; +} __packed_ws__(G_OverallTimeLimitExpired_Ep3_CAx3A, 0x10); // 6xB4x3B: Load current environment // This command is used to send spectators in a spectator team to the main @@ -6758,7 +6756,7 @@ struct G_OverallTimeLimitExpired_Ep3_CAx3A { struct G_LoadCurrentEnvironment_Ep3_6xB4x3B { G_CardBattleCommandHeader header = {0xB4, sizeof(G_LoadCurrentEnvironment_Ep3_6xB4x3B) / 4, 0, 0x3B, 0, 0, 0}; parray unused; -} __packed__; +} __packed_ws__(G_LoadCurrentEnvironment_Ep3_6xB4x3B, 0x0C); // 6xB5x3C: Set player substatus // This command sets the text that appears under the player's name in the HUD. @@ -6773,7 +6771,7 @@ struct G_SetPlayerSubstatus_Ep3_6xB5x3C { // 03 = At Counter uint8_t status = 0; parray unused; -} __packed__; +} __packed_ws__(G_SetPlayerSubstatus_Ep3_6xB5x3C, 0x0C); // 6xB4x3D: Set tournament player decks // This is sent before the counter sequence in a tournament game, to reserve the @@ -6790,7 +6788,7 @@ struct G_SetTournamentPlayerDecks_Ep3_6xB4x3D_Entry { /* 66 */ le_uint16_t unknown_a2 = 0; /* 68 */ le_uint16_t unknown_a3 = 0; /* 6A */ -} __packed__; +} __packed_ws__(G_SetTournamentPlayerDecks_Ep3_6xB4x3D_Entry, 0x6A); struct G_SetTournamentPlayerDecks_Ep3NTE_6xB4x3D { /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTournamentPlayerDecks_Ep3NTE_6xB4x3D) / 4, 0, 0x3D, 0, 0, 0}; @@ -6802,7 +6800,7 @@ struct G_SetTournamentPlayerDecks_Ep3NTE_6xB4x3D { /* 01C2 */ uint8_t unknown_a4 = 0; /* 01C3 */ uint8_t unknown_a5 = 0; /* 01C4 */ -} __packed__; +} __packed_ws__(G_SetTournamentPlayerDecks_Ep3NTE_6xB4x3D, 0x1C4); struct G_SetTournamentPlayerDecks_Ep3_6xB4x3D { /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTournamentPlayerDecks_Ep3_6xB4x3D) / 4, 0, 0x3D, 0, 0, 0}; @@ -6814,7 +6812,7 @@ struct G_SetTournamentPlayerDecks_Ep3_6xB4x3D { /* 01CA */ uint8_t unknown_a4 = 0; /* 01CB */ uint8_t unknown_a5 = 0; /* 01CC */ -} __packed__; +} __packed_ws__(G_SetTournamentPlayerDecks_Ep3_6xB4x3D, 0x1CC); // 6xB5x3E: Make card auction bid @@ -6824,7 +6822,7 @@ struct G_MakeCardAuctionBid_Ep3_6xB5x3E { uint8_t card_index = 0; // Index of card in EF command uint8_t bid_value = 0; // 1-99 parray unused; -} __packed__; +} __packed_ws__(G_MakeCardAuctionBid_Ep3_6xB5x3E, 0x0C); // 6xB5x3F: Open blocking menu // This command opens a shared menu between all clients in a game. The client @@ -6843,14 +6841,14 @@ struct G_OpenBlockingMenu_Ep3_6xB5x3F { parray unused1; le_uint32_t unknown_a3 = 0; parray unused2; -} __packed__; +} __packed_ws__(G_OpenBlockingMenu_Ep3_6xB5x3F, 0x14); // 6xB3x40 / CAx40: Request map list // The server should respond with a 6xB6x40 command. struct G_MapListRequest_Ep3_CAx40 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapListRequest_Ep3_CAx40) / 4, 0, 0x40, 0, 0, 0, 0, 0}; -} __packed__; +} __packed_ws__(G_MapListRequest_Ep3_CAx40, 0x10); // 6xB3x41 / CAx41: Request map data // The server should respond with a 6xB6x41 command containing the definition of @@ -6859,7 +6857,7 @@ struct G_MapListRequest_Ep3_CAx40 { struct G_MapDataRequest_Ep3_CAx41 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_MapDataRequest_Ep3_CAx41) / 4, 0, 0x41, 0, 0, 0, 0, 0}; le_uint32_t map_number = 0; -} __packed__; +} __packed_ws__(G_MapDataRequest_Ep3_CAx41, 0x14); // 6xB5x42: Initiate card auction // Sending this command to a client has the same effect as sending a 6xB5x3F @@ -6874,7 +6872,7 @@ struct G_MapDataRequest_Ep3_CAx41 { struct G_InitiateCardAuction_Ep3_6xB5x42 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_InitiateCardAuction_Ep3_6xB5x42) / 4, 0, 0x42, 0, 0, 0}; // This command uses header.unknown_a1 (probably for the client's ID). -} __packed__; +} __packed_ws__(G_InitiateCardAuction_Ep3_6xB5x42, 8); // 6xB5x43: Unknown // This command stores the card IDs and counts in a global array on the client, @@ -6889,9 +6887,9 @@ struct G_Unknown_Ep3_6xB5x43 { // XOR the values here with 0x39AB. le_uint16_t masked_card_id = 0xFFFF; // Must be < 0x2F1 (when unmasked) le_uint16_t masked_count = 0; // Must be in [1, 99] (when unmasked) - } __packed__; + } __packed_ws__(Entry, 4); parray entries; -} __packed__; +} __packed_ws__(G_Unknown_Ep3_6xB5x43, 0x58); // 6xB5x44: Card auction bid summary @@ -6899,7 +6897,7 @@ struct G_CardAuctionBidSummary_Ep3_6xB5x44 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionBidSummary_Ep3_6xB5x44) / 4, 0, 0x44, 0, 0, 0}; // Note: This command uses header.unknown_a1 for the bidder's client ID. parray bids; // In same order as cards in the EF command -} __packed__; +} __packed_ws__(G_CardAuctionBidSummary_Ep3_6xB5x44, 0x18); // 6xB5x45: Card auction results @@ -6909,7 +6907,7 @@ struct G_CardAuctionResults_Ep3_6xB5x45 { // This array is indexed by [card_index][client_id], and contains the final // bid for each player on each card (or 0 if they did not bid on that card). parray, 8> bids_by_player; -} __packed__; +} __packed_ws__(G_CardAuctionResults_Ep3_6xB5x45, 0x48); // 6xB4x46: Server version strings // This command doesn't seem to be necessary to actually play the game; the @@ -6944,7 +6942,7 @@ struct G_ServerVersionStrings_Ep3_6xB4x46 { // uninitialized memory bug in the server implementation (of which there are // many other examples). le_uint32_t unused = 0; -} __packed__; +} __packed_ws__(G_ServerVersionStrings_Ep3_6xB4x46, 0xCC); struct G_ServerVersionStrings_Ep3NTE_6xB4x46 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_ServerVersionStrings_Ep3NTE_6xB4x46) / 4, 0, 0x46, 0, 0, 0}; @@ -6953,7 +6951,7 @@ struct G_ServerVersionStrings_Ep3NTE_6xB4x46 { pstring version_signature; // "Jun 11 2003 05:02:36" pstring date_str1; -} __packed__; +} __packed_ws__(G_ServerVersionStrings_Ep3NTE_6xB4x46, 0x88); // 6xB5x47: Set spectator's CARD level // header.sender_client_id is the spectator's client ID. @@ -6961,7 +6959,7 @@ struct G_ServerVersionStrings_Ep3NTE_6xB4x46 { struct G_SetSpectatorCARDLevel_Ep3_6xB5x47 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetSpectatorCARDLevel_Ep3_6xB5x47) / 4, 0, 0x47, 0, 0, 0}; le_uint32_t clv = 0; -} __packed__; +} __packed_ws__(G_SetSpectatorCARDLevel_Ep3_6xB5x47, 0x0C); // 6xB3x48 / CAx48: End turn @@ -6969,7 +6967,7 @@ struct G_EndTurn_Ep3_CAx48 { G_CardServerDataCommandHeader header = {0xB3, sizeof(G_EndTurn_Ep3_CAx48) / 4, 0, 0x48, 0, 0, 0, 0, 0}; uint8_t client_id = 0; parray unused2; -} __packed__; +} __packed_ws__(G_EndTurn_Ep3_CAx48, 0x14); // 6xB3x49 / CAx49: Card counts // This command is sent when a client joins a game, but it is completely ignored @@ -6990,7 +6988,7 @@ struct G_CardCounts_Ep3_CAx49 { // This is encrypted with the trivial algorithm (see decrypt_trivial_gci_data) // using the basis in the preceding field parray card_id_to_count; -} __packed__; +} __packed_ws__(G_CardCounts_Ep3_CAx49, 0x304); // 6xB4x4A: Add to set card log // This command is not valid on Episode 3 Trial Edition. @@ -7005,7 +7003,7 @@ struct G_AddToSetCardlog_Ep3_6xB4x4A { uint8_t entry_count = 0; le_uint16_t round_num = 0; parray card_refs; -} __packed__; +} __packed_ws__(G_AddToSetCardlog_Ep3_6xB4x4A, 0x1C); // 6xB4x4B: Set EX result values // This command is not valid on Episode 3 Trial Edition. @@ -7035,10 +7033,10 @@ struct G_SetEXResultValues_Ep3_6xB4x4B { struct Entry { le_int16_t threshold = 0; le_int16_t value = 0; - } __packed__; + } __packed_ws__(Entry, 4); parray win_entries; parray lose_entries; -} __packed__; +} __packed_ws__(G_SetEXResultValues_Ep3_6xB4x4B, 0x58); // 6xB4x4C: Update action chain // This command is not valid on Episode 3 Trial Edition. @@ -7049,7 +7047,7 @@ struct G_UpdateActionChain_Ep3_6xB4x4C { int8_t index = 0; parray unused; Episode3::ActionChain chain; -} __packed__; +} __packed_ws__(G_UpdateActionChain_Ep3_6xB4x4C, 0x7C); // 6xB4x4D: Update action metadata // This command is not valid on Episode 3 Trial Edition. @@ -7060,7 +7058,7 @@ struct G_UpdateActionMetadata_Ep3_6xB4x4D { int8_t index = 0; parray unused; Episode3::ActionMetadata metadata; -} __packed__; +} __packed_ws__(G_UpdateActionMetadata_Ep3_6xB4x4D, 0x80); // 6xB4x4E: Update card conditions // This command is not valid on Episode 3 Trial Edition. @@ -7071,7 +7069,7 @@ struct G_UpdateCardConditions_Ep3_6xB4x4E { int8_t index = 0; parray unused; parray conditions; -} __packed__; +} __packed_ws__(G_UpdateCardConditions_Ep3_6xB4x4E, 0x9C); // 6xB4x4F: Clear set card conditions // This command is not valid on Episode 3 Trial Edition. @@ -7085,7 +7083,7 @@ struct G_ClearSetCardConditions_Ep3_6xB4x4F { // corresponds to set slot 0, the next bit to set slot 1, etc. (The upper 7 // bits of this field are unused.) le_uint16_t clear_mask = 0; -} __packed__; +} __packed_ws__(G_ClearSetCardConditions_Ep3_6xB4x4F, 0x0C); // 6xB4x50: Set trap tile locations // This command is not valid on Episode 3 Trial Edition. @@ -7097,7 +7095,7 @@ struct G_SetTrapTileLocations_Ep3_6xB4x50 { // location entry is FF FF. parray, 5> locations; parray unused; -} __packed__; +} __packed_ws__(G_SetTrapTileLocations_Ep3_6xB4x50, 0x14); // 6xB4x51: Tournament match result // This command is not valid on Episode 3 Trial Edition. @@ -7112,7 +7110,7 @@ struct G_TournamentMatchResult_Ep3_6xB4x51 { struct NamesEntry { pstring team_name; parray, 2> player_names; - } __packed__; + } __packed_ws__(NamesEntry, 0x40); parray names_entries; le_uint16_t unused1 = 0; // If round_num is equal to 6, the "On to the next battle..." text is replaced @@ -7126,7 +7124,7 @@ struct G_TournamentMatchResult_Ep3_6xB4x51 { // which is replaced with meseta_amount. The results screen animates this text // counting up from 0 to meseta_amount. pstring meseta_reward_text; -} __packed__; +} __packed_ws__(G_TournamentMatchResult_Ep3_6xB4x51, 0xF4); // 6xB4x52: Set game metadata // This command is not valid on Episode 3 Trial Edition. @@ -7158,7 +7156,7 @@ struct G_SetGameMetadata_Ep3_6xB4x52 { // usual message ("Viewing Battle", "Time left: XX:XX", and the like). le_uint16_t text_size = 0; pstring text; -} __packed__; +} __packed_ws__(G_SetGameMetadata_Ep3_6xB4x52, 0x110); // 6xB4x53: Reject battle start request // This command is not valid on Episode 3 Trial Edition. @@ -7174,4 +7172,17 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 { Episode3::RegistrationPhase registration_phase; parray unused; Episode3::MapAndRulesState state; -} __packed__; +} __packed_ws__(G_RejectBattleStartRequest_Ep3_6xB4x53, 0x144); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// EXTENDED COMMANDS /////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// These commands are not part of the official protocol; newserv uses these to +// implement extended functionality. + +// 30 (C->S): Extended player info +// Requested with the GetExtendedPlayerInfo patch. +// Format is PSOGCCharacterFile::Character. diff --git a/src/CommonItemSet.cc b/src/CommonItemSet.cc index 695a4858..f54993b3 100644 --- a/src/CommonItemSet.cc +++ b/src/CommonItemSet.cc @@ -124,7 +124,7 @@ void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) { using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; - const auto& offsets = r.pget>(r.pget(r.size() - 0x10)); + const auto& offsets = r.pget>(r.pget(r.size() - 0x10)); this->base_weapon_type_prob_table = r.pget>(offsets.base_weapon_type_prob_table_offset); this->subtype_base_table = r.pget>(offsets.subtype_base_table_offset); diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index 90f40ac9..693d536f 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -20,7 +20,7 @@ public: struct Range { IntT min; IntT max; - } __attribute__((packed)); + } __packed__; parray base_weapon_type_prob_table; parray subtype_base_table; @@ -53,7 +53,7 @@ public: void parse_itempt_t(const StringReader& r, bool is_v3); template - struct Offsets { + struct OffsetsT { using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; @@ -254,7 +254,11 @@ public: /* 50 */ U32T box_item_class_prob_table_offset; // There are several unused fields here. - } __attribute__((packed)); + } __packed__; + using Offsets = OffsetsT; + using OffsetsBE = OffsetsT; + check_struct_size(Offsets, 0x54); + check_struct_size(OffsetsBE, 0x54); }; std::shared_ptr get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; @@ -327,10 +331,12 @@ public: struct WeightTableEntry { ValueT value; WeightT weight; - } __attribute__((packed)); + } __packed__; using WeightTableEntry8 = WeightTableEntry; using WeightTableEntry32 = WeightTableEntry; + check_struct_size(WeightTableEntry8, 2); + check_struct_size(WeightTableEntry32, 8); protected: std::shared_ptr data; @@ -340,7 +346,7 @@ protected: be_uint32_t offset; uint8_t entries_per_table; parray unused; - } __attribute__((packed)); + } __packed_ws__(TableSpec, 8); RELFileSet(std::shared_ptr data); @@ -381,7 +387,7 @@ public: Mode mode; uint8_t player_level_divisor_or_min_level; uint8_t max_level; - } __attribute__((packed)); + } __packed_ws__(TechDiskLevelEntry, 3); std::pair get_common_recovery_table(size_t index) const; std::pair get_rare_recovery_table(size_t index) const; @@ -403,7 +409,7 @@ public: struct RangeTableEntry { be_uint32_t min; be_uint32_t max; - } __attribute__((packed)); + } __packed_ws__(RangeTableEntry, 8); std::pair get_weapon_type_table(size_t index) const; const parray* get_bonus_type_table(size_t which, size_t index) const; @@ -422,7 +428,7 @@ private: be_uint32_t special_mode_table; // [[{u32 value, u32 weight}](3)](8) be_uint32_t standard_grind_range_table; // [{u32 min, u32 max}](6) be_uint32_t favored_grind_range_table; // [{u32 min, u32 max}](6) - } __attribute__((packed)); + } __packed_ws__(Offsets, 0x20); const Offsets* offsets; }; @@ -455,11 +461,11 @@ private: uint8_t delta_index; uint8_t count_default; uint8_t count_favored; - } __attribute__((packed)); + } __packed_ws__(DeltaProbabilityEntry, 3); struct LuckTableEntry { uint8_t delta_index; int8_t luck; - } __attribute__((packed)); + } __packed_ws__(LuckTableEntry, 2); struct Offsets { // Each section ID's favored weapon class has different probabilities than @@ -565,7 +571,7 @@ private: // In PSO V3, the bonus delta luck table is: // +10 => +15, +5 => +8, 0 => 0, -5 => -8, -10 => -15 be_uint32_t bonus_delta_luck_offset; // LuckTableEntry[...]; ending with FF FF - } __attribute__((packed)); + } __packed_ws__(Offsets, 0x18); const Offsets* offsets; diff --git a/src/Episode3/BattleRecord.hh b/src/Episode3/BattleRecord.hh index 92ab34df..562d5aa9 100644 --- a/src/Episode3/BattleRecord.hh +++ b/src/Episode3/BattleRecord.hh @@ -26,7 +26,7 @@ public: le_uint32_t level; void print(FILE* stream) const; - } __attribute__((packed)); + } __packed_ws__(PlayerEntry, 0x440); struct Event { enum class Type : uint8_t { diff --git a/src/Episode3/CardSpecial.hh b/src/Episode3/CardSpecial.hh index 96d9debc..ef1b970f 100644 --- a/src/Episode3/CardSpecial.hh +++ b/src/Episode3/CardSpecial.hh @@ -6,6 +6,7 @@ #include "../Text.hh" #include "DataIndexes.hh" +#include "Server.hh" namespace Episode3 { @@ -94,7 +95,7 @@ public: void print(FILE* stream) const; uint32_t at(size_t index) const; - } __attribute__((packed)); + } __packed_ws__(AttackEnvStats, 0x9C); CardSpecial(std::shared_ptr server); std::shared_ptr server(); diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 44d59528..f173646f 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -447,7 +447,7 @@ struct Location { void clear(); void clear_FF(); -} __attribute__((packed)); +} __packed_ws__(Location, 4); struct CardDefinition { struct Stat { @@ -470,7 +470,7 @@ struct CardDefinition { void decode_code(); std::string str() const; JSON json() const; - } __attribute__((packed)); + } __packed_ws__(Stat, 4); struct Effect { // effect_num is the 1-based index of this effect within the card definition @@ -507,7 +507,7 @@ struct CardDefinition { static std::string str_for_arg(const std::string& arg); std::string str(const char* separator = ", ", const TextSet* text_archive = nullptr) const; JSON json() const; - } __attribute__((packed)); + } __packed_ws__(Effect, 0x20); /* 0000 */ be_uint32_t card_id; /* 0004 */ pstring jp_name; @@ -774,7 +774,7 @@ struct CardDefinition { void decode_range(); std::string str(bool single_line = true, const TextSet* text_archive = nullptr) const; JSON json() const; -} __attribute__((packed)); // 0x128 bytes in total +} __packed_ws__(CardDefinition, 0x128); struct CardDefinitionsFooter { // Technically the card definitions file is a REL file, so the last 0x20 bytes @@ -790,7 +790,7 @@ struct CardDefinitionsFooter { /* 48 */ be_uint32_t footer_offset; /* 4C */ parray unused2; /* 58 */ -} __attribute__((packed)); +} __packed_ws__(CardDefinitionsFooter, 0x58); struct DeckDefinition { /* 00 */ pstring name; @@ -810,7 +810,7 @@ struct DeckDefinition { /* 82 */ uint8_t second; /* 83 */ uint8_t unknown_a2; /* 84 */ -} __attribute__((packed)); +} __packed_ws__(DeckDefinition, 0x84); struct PlayerConfig { // The game splits this internally into two structures. The first column of @@ -828,7 +828,7 @@ struct PlayerConfig { // earlier version, this was the offline records structure, but they later // decided to just count online and offline records together in the main // records structure and didn't remove the codepath that reads from this. - /* 0138:---- */ PlayerRecords_Battle unused_offline_records; + /* 0138:---- */ PlayerRecordsBattleBE unused_offline_records; /* 0150:---- */ parray unknown_a4; // The PlayerDataSegment structure begins here. In newserv, we combine this // structure into PlayerConfig since the two are always used together. @@ -879,7 +879,7 @@ struct PlayerConfig { struct PlayerReference { /* 00 */ be_uint32_t guild_card_number; /* 04 */ pstring name; - } __attribute__((packed)); + } __packed_ws__(PlayerReference, 0x1C); // This array is updated when a battle is started (via a 6xB4x05 command). The // client adds the opposing players' info to ths first two entries here if the // opponents are human. (The existing entries are always moved back by two @@ -902,7 +902,7 @@ struct PlayerConfig { void decrypt(); void encrypt(uint8_t basis); -} __attribute__((packed)); +} __packed_ws__(PlayerConfig, 0x2350); enum class HPType : uint8_t { DEFEAT_PLAYER = 0, @@ -972,7 +972,7 @@ struct Rules { std::pair def_dice_range(bool is_1p_2v1) const; std::string str() const; -} __attribute__((packed)); +} __packed_ws__(Rules, 0x14); struct RulesTrial { // Most fields here have the same meanings as in the final version. @@ -996,7 +996,7 @@ struct RulesTrial { RulesTrial() = default; RulesTrial(const Rules&); operator Rules() const; -} __attribute__((packed)); +} __packed_ws__(RulesTrial, 0x0C); struct StateFlags { /* 00 */ le_uint16_t turn_num; @@ -1018,7 +1018,7 @@ struct StateFlags { bool operator!=(const StateFlags& other) const; void clear(); void clear_FF(); -} __attribute__((packed)); +} __packed_ws__(StateFlags, 0x18); struct MapList { be_uint32_t num_maps; @@ -1046,18 +1046,18 @@ struct MapList { /* 021C */ uint8_t map_category; /* 021D */ parray unused; /* 0220 */ - } __attribute__((packed)); + } __packed_ws__(Entry, 0x220); // Variable-length fields: // Entry entries[num_maps]; // char strings[...EOF]; // Null-terminated strings, pointed to by offsets in Entry structs -} __attribute__((packed)); +} __packed_ws__(MapList, 0x10); struct CompressedMapHeader { // .mnm file format le_uint32_t map_number; le_uint32_t compressed_data_size; // Compressed data immediately follows (which decompresses to a MapDefinition) -} __attribute__((packed)); +} __packed_ws__(CompressedMapHeader, 8); struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // If tag is not 0x00000100, the game considers the map to be corrupt in @@ -1155,7 +1155,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests std::string str() const; JSON json() const; - } __attribute__((packed)); + } __packed_ws__(CameraSpec, 0x48); // This array specifies the camera zone maps. A camera zone map is a subset of // the main map (specified in map_tiles). Tiles that are part of each camera @@ -1213,7 +1213,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 18 */ parray card_ids; // Last one appears to always be FFFF /* 58 */ JSON json(uint8_t language) const; - } __attribute__((packed)); + } __packed_ws__(NPCDeck, 0x58); /* 1FE8 */ parray npc_decks; // Unused if name[0] == 0 // These are almost (but not quite) the same format as the entries in @@ -1229,7 +1229,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 0018 */ parray params; /* 0114 */ JSON json(uint8_t language) const; - } __attribute__((packed)); + } __packed_ws__(AIParams, 0x114); /* 20F0 */ parray npc_ai_params; // Unused if name[0] == 0 /* 242C */ parray unknown_a7; @@ -1282,7 +1282,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 0004 */ parray, 4> strings; /* 0104 */ JSON json(uint8_t language) const; - } __attribute__((packed)); + } __packed_ws__(DialogueSet, 0x104); // There are up to 0x10 of these per valid NPC, but only the first 13 of them // are used, since each one must have a unique value for .when and the values @@ -1365,7 +1365,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests bool operator==(const EntryState& other) const = default; bool operator!=(const EntryState& other) const = default; JSON json() const; - } __attribute__((packed)); + } __packed_ws__(EntryState, 2); /* 5A10 */ parray entry_states; /* 5A18 */ @@ -1381,7 +1381,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests std::string str(const CardIndex* card_index, uint8_t language) const; JSON json(uint8_t language) const; -} __attribute__((packed)); +} __packed_ws__(MapDefinition, 0x5A18); struct MapDefinitionTrial { // This is the format of Episode 3 Trial Edition maps. See the comments in @@ -1430,7 +1430,7 @@ struct MapDefinitionTrial { MapDefinitionTrial(const MapDefinition& map); operator MapDefinition() const; -} __attribute__((packed)); +} __packed_ws__(MapDefinitionTrial, 0x41A0); struct COMDeckDefinition { size_t index; diff --git a/src/Episode3/DeckState.hh b/src/Episode3/DeckState.hh index 3e424605..7dcc3391 100644 --- a/src/Episode3/DeckState.hh +++ b/src/Episode3/DeckState.hh @@ -20,7 +20,7 @@ struct NameEntry { NameEntry(); void clear(); -} __attribute__((packed)); +} __packed_ws__(NameEntry, 0x14); struct DeckEntry { /* 00 */ pstring name; @@ -37,7 +37,7 @@ struct DeckEntry { DeckEntry(); void clear(); -} __attribute__((packed)); +} __packed_ws__(DeckEntry, 0x58); uint8_t index_for_card_ref(uint16_t card_ref); uint8_t client_id_for_card_ref(uint16_t card_ref); diff --git a/src/Episode3/MapState.hh b/src/Episode3/MapState.hh index 752e17bd..c8edf274 100644 --- a/src/Episode3/MapState.hh +++ b/src/Episode3/MapState.hh @@ -20,7 +20,7 @@ struct MapState { void clear(); void print(FILE* stream) const; -} __attribute__((packed)); +} __packed_ws__(MapState, 0x110); struct MapAndRulesState { /* 0000 */ MapState map; @@ -45,7 +45,7 @@ struct MapAndRulesState { void set_occupied_bit_for_tile(uint8_t x, uint8_t y); void clear_occupied_bit_for_tile(uint8_t x, uint8_t y); -} __attribute__((packed)); +} __packed_ws__(MapAndRulesState, 0x138); struct MapAndRulesStateTrial { /* 0000 */ MapState map; @@ -65,7 +65,7 @@ struct MapAndRulesStateTrial { MapAndRulesStateTrial() = default; MapAndRulesStateTrial(const MapAndRulesState& state); operator MapAndRulesState() const; -} __attribute__((packed)); +} __packed_ws__(MapAndRulesStateTrial, 0x130); struct OverlayState { parray, 0x10> tiles; @@ -75,6 +75,6 @@ struct OverlayState { OverlayState(); void clear(); -} __attribute__((packed)); +} __packed_ws__(OverlayState, 0x174); } // namespace Episode3 diff --git a/src/Episode3/PlayerStateSubordinates.hh b/src/Episode3/PlayerStateSubordinates.hh index 5987a133..9be7556d 100644 --- a/src/Episode3/PlayerStateSubordinates.hh +++ b/src/Episode3/PlayerStateSubordinates.hh @@ -36,7 +36,7 @@ struct Condition { void clear_FF(); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(Condition, 0x10); struct EffectResult { /* 00 */ le_uint16_t attacker_card_ref; @@ -58,7 +58,7 @@ struct EffectResult { void clear(); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(EffectResult, 0x0C); struct CardShortStatus { /* 00 */ le_uint16_t card_ref; @@ -78,7 +78,7 @@ struct CardShortStatus { void clear_FF(); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(CardShortStatus, 0x10); struct ActionState { /* 00 */ le_uint16_t client_id; @@ -99,7 +99,7 @@ struct ActionState { void clear(); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(ActionState, 0x64); struct ActionChain { // Note: Episode 3 Trial Edition has a different format for this structure. @@ -135,7 +135,7 @@ struct ActionChain { void clear_FF(); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(ActionChain, 0x70); struct ActionChainWithConds { /* 0000 */ ActionChain chain; @@ -173,7 +173,7 @@ struct ActionChainWithConds { uint8_t get_adjusted_move_ability_nte(uint8_t ability) const; std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(ActionChainWithConds, 0x100); struct ActionChainWithCondsTrial { /* 0000 */ int8_t effective_ap; @@ -205,7 +205,7 @@ struct ActionChainWithCondsTrial { ActionChainWithCondsTrial() = default; ActionChainWithCondsTrial(const ActionChainWithConds& src); operator ActionChainWithConds() const; -} __attribute__((packed)); +} __packed_ws__(ActionChainWithCondsTrial, 0x100); struct ActionMetadata { /* 00 */ le_uint16_t card_ref; @@ -241,7 +241,7 @@ struct ActionMetadata { uint16_t original_attacker_card_ref); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(ActionMetadata, 0x74); struct HandAndEquipState { /* 00 */ parray dice_results; @@ -276,7 +276,7 @@ struct HandAndEquipState { void clear_FF(); std::string str(std::shared_ptr s) const; -} __attribute__((packed)); +} __packed_ws__(HandAndEquipState, 0x54); struct PlayerBattleStats { /* 00 */ le_uint16_t damage_given; @@ -310,7 +310,7 @@ struct PlayerBattleStats { static uint8_t rank_for_score(float score); static const char* name_for_rank(uint8_t rank); -} __attribute__((packed)); +} __packed_ws__(PlayerBattleStats, 0x28); struct PlayerBattleStatsTrial { /* 00 */ le_uint32_t damage_given = 0; @@ -323,7 +323,7 @@ struct PlayerBattleStatsTrial { PlayerBattleStatsTrial() = default; PlayerBattleStatsTrial(const PlayerBattleStats& data); operator PlayerBattleStats() const; -} __attribute__((packed)); +} __packed_ws__(PlayerBattleStatsTrial, 0x14); std::vector get_card_refs_within_range( const parray& range, diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index fc0ba7cc..5664ba30 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -290,7 +290,8 @@ public: uint8_t is_cpu_player; PresenceEntry(); void clear(); - } __attribute__((packed)); + } __packed_ws__(PresenceEntry, 3); + std::shared_ptr map_and_rules; bcarray, 4> deck_entries; parray presence_entries; diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index 369012b8..96019062 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -440,7 +440,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) { char developer_code2 = 'P'; uint8_t disc_number = 0; uint8_t version_code; - } __attribute__((packed)) data; + } __packed__ data; for (const char* game_code2 = "OS"; *game_code2; game_code2++) { data.game_code2 = *game_code2; for (const char* region_code = "JEP"; *region_code; region_code++) { diff --git a/src/GSLArchive.cc b/src/GSLArchive.cc index 776b81ef..61260678 100644 --- a/src/GSLArchive.cc +++ b/src/GSLArchive.cc @@ -9,21 +9,26 @@ using namespace std; template -struct GSLHeaderEntry { +struct GSLHeaderEntryT { using U32T = typename std::conditional::type; pstring filename; U32T offset; // In pages, so actual offset is this * 0x800 U32T size; uint64_t unused; -} __attribute__((packed)); +} __packed__; + +using GSLHeaderEntry = GSLHeaderEntryT; +using GSLHeaderEntryBE = GSLHeaderEntryT; +check_struct_size(GSLHeaderEntry, 0x30); +check_struct_size(GSLHeaderEntryBE, 0x30); template void GSLArchive::load_t() { StringReader r(*this->data); uint64_t min_data_offset = 0xFFFFFFFFFFFFFFFF; while (r.where() < min_data_offset) { - const auto& entry = r.get>(); + const auto& entry = r.get>(); if (entry.filename.empty()) { break; } @@ -85,10 +90,10 @@ string GSLArchive::generate_t(const unordered_map& files) { // Make sure there's enough space for a blank header entry before any file's // data pages begin - uint32_t data_start_offset = ((sizeof(GSLHeaderEntry) * (files.size() + 1)) + 0x7FF) & (~0x7FF); + uint32_t data_start_offset = ((sizeof(GSLHeaderEntryT) * (files.size() + 1)) + 0x7FF) & (~0x7FF); uint32_t data_offset = data_start_offset; for (const auto& file : files) { - GSLHeaderEntry entry; + GSLHeaderEntryT entry; entry.filename.encode(file.first); entry.offset = data_offset >> 11; entry.size = file.second.size(); diff --git a/src/GVMEncoder.cc b/src/GVMEncoder.cc index 396afe2d..4a7449c4 100644 --- a/src/GVMEncoder.cc +++ b/src/GVMEncoder.cc @@ -12,14 +12,14 @@ struct GVMFileEntry { be_uint16_t file_num; pstring name; parray unknown_a1; -} __attribute__((packed)); +} __packed_ws__(GVMFileEntry, 0x26); struct GVMFileHeader { be_uint32_t magic; // 'GVMH' le_uint32_t header_size; be_uint16_t flags; be_uint16_t num_files; -} __attribute__((packed)); +} __packed_ws__(GVMFileHeader, 0x0C); struct GVRHeader { be_uint32_t magic; // 'GVRT' @@ -29,7 +29,7 @@ struct GVRHeader { GVRDataFormat data_format; be_uint16_t width; be_uint16_t height; -} __attribute__((packed)); +} __packed_ws__(GVRHeader, 0x10); string encode_gvm(const Image& img, GVRDataFormat data_format) { if (img.get_width() > 0xFFFF) { diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 29c236f6..ffcb722f 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -380,10 +380,10 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr c, shared ret.emplace("BattleDisconnectCount", p->battle_records.disconnect_count.load()); if (!is_ep3(c->version())) { - auto json_for_challenge_times = [](const parray, Count>& times) -> JSON { + auto json_for_challenge_times = [](const parray& times) -> JSON { auto times_json = JSON::list(); for (size_t z = 0; z < times.size(); z++) { - times_json.emplace_back(times[z].load()); + times_json.emplace_back(times[z].decode()); } return times_json; }; @@ -419,11 +419,11 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr c, shared ret.emplace("ChallengeGraveTeam", p->challenge_records.grave_team.decode()); ret.emplace("ChallengeGraveMessage", p->challenge_records.grave_message.decode()); ret.emplace("ChallengeAwardStateEp1OnlineFlags", p->challenge_records.ep1_online_award_state.rank_award_flags.load()); - ret.emplace("ChallengeAwardStateEp1OnlineMaxRank", p->challenge_records.ep1_online_award_state.maximum_rank.load()); + ret.emplace("ChallengeAwardStateEp1OnlineMaxRank", p->challenge_records.ep1_online_award_state.maximum_rank.decode()); ret.emplace("ChallengeAwardStateEp2OnlineFlags", p->challenge_records.ep2_online_award_state.rank_award_flags.load()); - ret.emplace("ChallengeAwardStateEp2OnlineMaxRank", p->challenge_records.ep2_online_award_state.maximum_rank.load()); + ret.emplace("ChallengeAwardStateEp2OnlineMaxRank", p->challenge_records.ep2_online_award_state.maximum_rank.decode()); ret.emplace("ChallengeAwardStateEp1OfflineFlags", p->challenge_records.ep1_offline_award_state.rank_award_flags.load()); - ret.emplace("ChallengeAwardStateEp1OfflineMaxRank", p->challenge_records.ep1_offline_award_state.maximum_rank.load()); + ret.emplace("ChallengeAwardStateEp1OfflineMaxRank", p->challenge_records.ep1_offline_award_state.maximum_rank.decode()); ret.emplace("ChallengeRankTitle", p->challenge_records.rank_title.decode()); } } diff --git a/src/IPFrameInfo.hh b/src/IPFrameInfo.hh index a3a2e92b..29cd55a1 100644 --- a/src/IPFrameInfo.hh +++ b/src/IPFrameInfo.hh @@ -12,31 +12,31 @@ struct HDLCHeader { uint8_t address; // 0xFF usually uint8_t control; // 0x03 for PPP be_uint16_t protocol; -} __attribute__((packed)); +} __packed_ws__(HDLCHeader, 5); struct LCPHeader { uint8_t command; uint8_t request_id; be_uint16_t size; -} __attribute__((packed)); +} __packed_ws__(LCPHeader, 4); struct PAPHeader { uint8_t command; uint8_t request_id; be_uint16_t size; -} __attribute__((packed)); +} __packed_ws__(PAPHeader, 4); struct IPCPHeader { uint8_t command; uint8_t request_id; be_uint16_t size; -} __attribute__((packed)); +} __packed_ws__(IPCPHeader, 4); struct EthernetHeader { parray dest_mac; parray src_mac; be_uint16_t protocol; -} __attribute__((packed)); +} __packed_ws__(EthernetHeader, 0x0E); struct ARPHeader { be_uint16_t hardware_type; @@ -44,7 +44,7 @@ struct ARPHeader { uint8_t hwaddr_len; uint8_t paddr_len; be_uint16_t operation; -} __attribute__((packed)); +} __packed_ws__(ARPHeader, 8); struct IPv4Header { uint8_t version_ihl; @@ -57,14 +57,14 @@ struct IPv4Header { be_uint16_t checksum; be_uint32_t src_addr; be_uint32_t dest_addr; -} __attribute__((packed)); +} __packed_ws__(IPv4Header, 0x14); struct UDPHeader { be_uint16_t src_port; be_uint16_t dest_port; be_uint16_t size; be_uint16_t checksum; -} __attribute__((packed)); +} __packed_ws__(UDPHeader, 8); struct TCPHeader { enum Flag { @@ -87,7 +87,7 @@ struct TCPHeader { be_uint16_t window; be_uint16_t checksum; be_uint16_t urgent_ptr; -} __attribute__((packed)); +} __packed_ws__(TCPHeader, 0x14); struct DHCPHeader { uint8_t opcode = 0; @@ -105,7 +105,7 @@ struct DHCPHeader { parray unused_bootp_legacy; be_uint32_t magic = 0x63825363; // Options follow here, terminated with FF -} __attribute__((packed)); +} __packed_ws__(DHCPHeader, 0xF0); struct FrameInfo { enum class LinkType { diff --git a/src/IntegralExpression.hh b/src/IntegralExpression.hh index 4c0fcaf5..fcc69db0 100644 --- a/src/IntegralExpression.hh +++ b/src/IntegralExpression.hh @@ -17,7 +17,7 @@ class IntegralExpression { public: struct Env { const QuestFlagsForDifficulty* flags; - const PlayerRecordsBB_Challenge* challenge_records; + const PlayerRecordsChallengeBB* challenge_records; std::shared_ptr team; size_t num_players; uint8_t event; diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 18f56944..512e14fe 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -78,7 +78,7 @@ private: struct UnitResult { uint8_t unit; int8_t modifier; - } __attribute__((packed)); + } __packed_ws__(UnitResult, 2); std::array, 13> unit_results_by_star_count; // Note: The original implementation uses 17 different random states for some diff --git a/src/ItemData.hh b/src/ItemData.hh index 8f6f204a..bef317e1 100644 --- a/src/ItemData.hh +++ b/src/ItemData.hh @@ -121,7 +121,7 @@ struct ItemData { parray data1wb; parray data1d; parray data1db; - } __attribute__((packed)); + } __packed__; le_uint32_t id; union { parray data2; @@ -129,7 +129,7 @@ struct ItemData { parray data2wb; le_uint32_t data2d; be_uint32_t data2db; - } __attribute__((packed)); + } __packed__; ItemData(); ItemData(const ItemData& other); @@ -195,4 +195,4 @@ struct ItemData { bool empty() const; static bool compare_for_sort(const ItemData& a, const ItemData& b); -} __attribute__((packed)); +} __packed_ws__(ItemData, 0x14); diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc index 0a5250cb..a3ea6a56 100644 --- a/src/ItemParameterTable.cc +++ b/src/ItemParameterTable.cc @@ -79,9 +79,9 @@ ItemParameterTable::ItemParameterTable(shared_ptr data, Version ve case Version::GC_V3: case Version::XB_V3: { if (is_big_endian(this->version)) { - this->offsets_v3_be = &this->r.pget>(offset_table_offset); + this->offsets_v3_be = &this->r.pget(offset_table_offset); } else { - this->offsets_v3_le = &this->r.pget>(offset_table_offset); + this->offsets_v3_le = &this->r.pget(offset_table_offset); } this->num_weapon_classes = 0xAA; this->num_tool_classes = 0x18; @@ -93,7 +93,7 @@ ItemParameterTable::ItemParameterTable(shared_ptr data, Version ve } case Version::BB_V4: { - this->offsets_v4 = &this->r.pget>(offset_table_offset); + this->offsets_v4 = &this->r.pget(offset_table_offset); this->num_weapon_classes = 0xED; this->num_tool_classes = 0x1B; this->item_stars_first_id = 0xB1; @@ -207,7 +207,7 @@ ItemParameterTable::WeaponV4 ItemParameterTable::WeaponGCNTE::to_v4() const { } template -ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3::to_v4() const { +ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3T::to_v4() const { WeaponV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); @@ -282,7 +282,7 @@ ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV1V2::to_v4 } template -ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3::to_v4() const { +ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3T::to_v4() const { ArmorOrShieldV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); @@ -324,7 +324,7 @@ ItemParameterTable::UnitV4 ItemParameterTable::UnitV1V2::to_v4() const { } template -ItemParameterTable::UnitV4 ItemParameterTable::UnitV3::to_v4() const { +ItemParameterTable::UnitV4 ItemParameterTable::UnitV3T::to_v4() const { UnitV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); @@ -371,7 +371,7 @@ ItemParameterTable::MagV4 ItemParameterTable::MagV2::to_v4() const { } template -ItemParameterTable::MagV4 ItemParameterTable::MagV3::to_v4() const { +ItemParameterTable::MagV4 ItemParameterTable::MagV3T::to_v4() const { MagV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); @@ -402,7 +402,7 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV1V2::to_v4() const { } template -ItemParameterTable::ToolV4 ItemParameterTable::ToolV3::to_v4() const { +ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T::to_v4() const { ToolV4 ret; ret.base.id = this->base.id.load(); ret.base.type = this->base.type.load(); @@ -416,13 +416,13 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV3::to_v4() cons template size_t indirect_lookup_2d_count(const StringReader& r, size_t root_offset, size_t co_index) { - using ArrayRefT = typename std::conditional_t; + using ArrayRefT = typename std::conditional_t; return r.pget(root_offset + sizeof(ArrayRefT) * co_index).count; } template const T& indirect_lookup_2d(const StringReader& r, size_t root_offset, size_t co_index, size_t item_index) { - using ArrayRefT = typename std::conditional_t; + using ArrayRefT = typename std::conditional_t; const auto& co = r.pget(root_offset + sizeof(ArrayRefT) * co_index); if (item_index >= co.count) { @@ -473,9 +473,9 @@ const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1 } else if (this->offsets_gc_nte) { def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->weapon_table, data1_1, data1_2).to_v4(); } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d, false>(this->r, this->offsets_v3_le->weapon_table, data1_1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->weapon_table, data1_1, data1_2).to_v4(); } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_v3_be->weapon_table, data1_1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->weapon_table, data1_1, data1_2).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } @@ -528,11 +528,11 @@ const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shie } else if (this->offsets_v1_v2) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d, false>(this->r, this->offsets_v3_le->armor_table, data1_1 - 1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->armor_table, data1_1 - 1, data1_2).to_v4(); } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_v3_be->armor_table, data1_1 - 1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->armor_table, data1_1 - 1, data1_2).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } @@ -580,11 +580,11 @@ const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2) } else if (this->offsets_v1_v2) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_gc_nte->unit_table, 0, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d, false>(this->r, this->offsets_v3_le->unit_table, 0, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->unit_table, 0, data1_2).to_v4(); } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_v3_be->unit_table, 0, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->unit_table, 0, data1_2).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } @@ -636,11 +636,11 @@ const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) co def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->mag_table, 0, data1_1).to_v4(); } } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_gc_nte->mag_table, 0, data1_1).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->mag_table, 0, data1_1).to_v4(); } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d, false>(this->r, this->offsets_v3_le->mag_table, 0, data1_1).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->mag_table, 0, data1_1).to_v4(); } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_v3_be->mag_table, 0, data1_1).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->mag_table, 0, data1_1).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } @@ -693,11 +693,11 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1, } else if (this->offsets_v1_v2) { def_v4 = indirect_lookup_2d(this->r, this->offsets_v1_v2->tool_table, data1_1, data1_2).to_v4(); } else if (this->offsets_gc_nte) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_gc_nte->tool_table, data1_1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_gc_nte->tool_table, data1_1, data1_2).to_v4(); } else if (this->offsets_v3_le) { - def_v4 = indirect_lookup_2d, false>(this->r, this->offsets_v3_le->tool_table, data1_1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_le->tool_table, data1_1, data1_2).to_v4(); } else if (this->offsets_v3_be) { - def_v4 = indirect_lookup_2d, true>(this->r, this->offsets_v3_be->tool_table, data1_1, data1_2).to_v4(); + def_v4 = indirect_lookup_2d(this->r, this->offsets_v3_be->tool_table, data1_1, data1_2).to_v4(); } else { throw logic_error("table is not v2, v3, or v4"); } @@ -706,13 +706,13 @@ const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1, } } -template +template pair ItemParameterTable::find_tool_by_id_t(uint32_t tool_table_offset, uint32_t item_id) const { - const auto* cos = &this->r.pget>( - tool_table_offset, this->num_tool_classes * sizeof(ArrayRef)); + const auto* cos = &this->r.pget>( + tool_table_offset, this->num_tool_classes * sizeof(ArrayRefT)); for (size_t z = 0; z < this->num_tool_classes; z++) { const auto& co = cos[z]; - const auto* defs = &this->r.pget(co.offset, sizeof(ToolT) * co.count); + const auto* defs = &this->r.pget(co.offset, sizeof(ToolDefT) * co.count); for (size_t y = 0; y < co.count; y++) { if (defs[y].base.id == item_id) { return make_pair(z, y); @@ -728,11 +728,11 @@ pair ItemParameterTable::find_tool_by_id(uint32_t item_id) con } else if (this->offsets_v1_v2) { return this->find_tool_by_id_t(this->offsets_v1_v2->tool_table, item_id); } else if (this->offsets_gc_nte) { - return this->find_tool_by_id_t, true>(this->offsets_gc_nte->tool_table, item_id); + return this->find_tool_by_id_t(this->offsets_gc_nte->tool_table, item_id); } else if (this->offsets_v3_le) { - return this->find_tool_by_id_t, false>(this->offsets_v3_le->tool_table, item_id); + return this->find_tool_by_id_t(this->offsets_v3_le->tool_table, item_id); } else if (this->offsets_v3_be) { - return this->find_tool_by_id_t, true>(this->offsets_v3_be->tool_table, item_id); + return this->find_tool_by_id_t(this->offsets_v3_be->tool_table, item_id); } else if (this->offsets_v4) { return this->find_tool_by_id_t(this->offsets_v4->tool_table, item_id); } else { @@ -752,7 +752,7 @@ float ItemParameterTable::get_sale_divisor_t(const OffsetsT* offsets, uint8_t da return this->r.pget(offsets->weapon_sale_divisor_table + data1_1 * sizeof(FloatT)); case 1: { - const auto& divisors = this->r.pget>(offsets->sale_divisor_table); + const auto& divisors = this->r.pget>(offsets->sale_divisor_table); switch (data1_1) { case 1: return divisors.armor_divisor; @@ -765,7 +765,7 @@ float ItemParameterTable::get_sale_divisor_t(const OffsetsT* offsets, uint8_t da } case 2: { - const auto& divisors = this->r.pget>(offsets->sale_divisor_table); + const auto& divisors = this->r.pget>(offsets->sale_divisor_table); return divisors.mag_divisor; } @@ -803,22 +803,22 @@ const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result uint32_t offset; if (this->offsets_dc_protos) { - const auto& table_offsets = this->r.pget>(this->offsets_dc_protos->mag_feed_table); + const auto& table_offsets = this->r.pget(this->offsets_dc_protos->mag_feed_table); offset = table_offsets.offsets[table_index]; } else if (this->offsets_v1_v2) { - const auto& table_offsets = this->r.pget>(this->offsets_v1_v2->mag_feed_table); + const auto& table_offsets = this->r.pget(this->offsets_v1_v2->mag_feed_table); offset = table_offsets.offsets[table_index]; } else if (this->offsets_gc_nte) { - const auto& table_offsets = this->r.pget>(this->offsets_gc_nte->mag_feed_table); + const auto& table_offsets = this->r.pget(this->offsets_gc_nte->mag_feed_table); offset = table_offsets.offsets[table_index]; } else if (this->offsets_v3_le) { - const auto& table_offsets = this->r.pget>(this->offsets_v3_le->mag_feed_table); + const auto& table_offsets = this->r.pget(this->offsets_v3_le->mag_feed_table); offset = table_offsets.offsets[table_index]; } else if (this->offsets_v3_be) { - const auto& table_offsets = this->r.pget>(this->offsets_v3_be->mag_feed_table); + const auto& table_offsets = this->r.pget(this->offsets_v3_be->mag_feed_table); offset = table_offsets.offsets[table_index]; } else if (this->offsets_v4) { - const auto& table_offsets = this->r.pget>(this->offsets_v4->mag_feed_table); + const auto& table_offsets = this->r.pget(this->offsets_v4->mag_feed_table); offset = table_offsets.offsets[table_index]; } else { throw logic_error("table is not v2, v3, or v4"); @@ -856,24 +856,24 @@ uint8_t ItemParameterTable::get_special_stars(uint8_t special) const { : 0; } -const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t special) const { +const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t special) const { special &= 0x3F; if (special >= this->num_specials) { throw out_of_range("invalid special index"); } if (this->offsets_dc_protos) { - return this->r.pget>(this->offsets_dc_protos->special_data_table + sizeof(Special) * special); + return this->r.pget(this->offsets_dc_protos->special_data_table + sizeof(Special) * special); } else if (this->offsets_v1_v2) { - return this->r.pget>(this->offsets_v1_v2->special_data_table + sizeof(Special) * special); + return this->r.pget(this->offsets_v1_v2->special_data_table + sizeof(Special) * special); } else if (this->offsets_v3_le) { - return this->r.pget>(this->offsets_v3_le->special_data_table + sizeof(Special) * special); + return this->r.pget(this->offsets_v3_le->special_data_table + sizeof(Special) * special); } else if (this->offsets_gc_nte) { if ((special >= this->parsed_specials.size()) || (this->parsed_specials[special].type != 0xFFFF)) { if (special >= this->parsed_specials.size()) { this->parsed_specials.resize(special + 1); } - const auto& sp_be = this->r.pget>(this->offsets_gc_nte->special_data_table + sizeof(Special) * special); + const auto& sp_be = this->r.pget(this->offsets_gc_nte->special_data_table + sizeof(SpecialBE) * special); this->parsed_specials[special].type = sp_be.type.load(); this->parsed_specials[special].amount = sp_be.amount.load(); } @@ -883,13 +883,13 @@ const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_ if (special >= this->parsed_specials.size()) { this->parsed_specials.resize(special + 1); } - const auto& sp_be = this->r.pget>(this->offsets_v3_be->special_data_table + sizeof(Special) * special); + const auto& sp_be = this->r.pget(this->offsets_v3_be->special_data_table + sizeof(SpecialBE) * special); this->parsed_specials[special].type = sp_be.type.load(); this->parsed_specials[special].amount = sp_be.amount.load(); } return this->parsed_specials[special]; } else if (this->offsets_v4) { - return this->r.pget>(this->offsets_v4->special_data_table + sizeof(Special) * special); + return this->r.pget(this->offsets_v4->special_data_table + sizeof(Special) * special); } else { throw logic_error("table is not v2, v3, or v4"); } @@ -1051,7 +1051,7 @@ bool ItemParameterTable::is_unsealable_item(uint8_t data1_0, uint8_t data1_1, ui if (this->offsets_dc_protos || this->offsets_v1_v2 || this->offsets_gc_nte) { return false; } else if (this->offsets_v3_le) { - const auto& co = this->r.pget(this->offsets_v3_le->unsealable_table); + const auto& co = this->r.pget(this->offsets_v3_le->unsealable_table); offset = co.offset; count = co.count; } else if (this->offsets_v3_be) { @@ -1059,7 +1059,7 @@ bool ItemParameterTable::is_unsealable_item(uint8_t data1_0, uint8_t data1_1, ui offset = co.offset; count = co.count; } else if (this->offsets_v4) { - const auto& co = this->r.pget(this->offsets_v4->unsealable_table); + const auto& co = this->r.pget(this->offsets_v4->unsealable_table); offset = co.offset; count = co.count; } else { @@ -1111,7 +1111,7 @@ const std::map>& Item static const std::map> empty_map; return empty_map; } else if (this->offsets_v3_le) { - const auto& co = this->r.pget(this->offsets_v3_le->combination_table); + const auto& co = this->r.pget(this->offsets_v3_le->combination_table); offset = co.offset; count = co.count; } else if (this->offsets_v3_be) { @@ -1119,7 +1119,7 @@ const std::map>& Item offset = co.offset; count = co.count; } else if (this->offsets_v4) { - const auto& co = this->r.pget(this->offsets_v4->combination_table); + const auto& co = this->r.pget(this->offsets_v4->combination_table); offset = co.offset; count = co.count; } else { @@ -1138,7 +1138,7 @@ const std::map>& Item template size_t ItemParameterTable::num_events_t(uint32_t base_offset) const { - return this->r.pget>(base_offset).count; + return this->r.pget>(base_offset).count; } size_t ItemParameterTable::num_events() const { @@ -1158,11 +1158,11 @@ size_t ItemParameterTable::num_events() const { template std::pair ItemParameterTable::get_event_items_t( uint32_t base_offset, uint8_t event_number) const { - const auto& co = this->r.pget>(base_offset); + const auto& co = this->r.pget>(base_offset); if (event_number >= co.count) { throw out_of_range("invalid event number"); } - const auto& event_co = this->r.pget>(co.offset + sizeof(ArrayRef) * event_number); + const auto& event_co = this->r.pget>(co.offset + sizeof(ArrayRefT) * event_number); const auto* defs = &this->r.pget(event_co.offset, event_co.count * sizeof(EventItem)); return make_pair(defs, event_co.count); } diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index c5edd1a0..a2c2f5b1 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -19,42 +19,43 @@ public: // being null or not in each public function. Rewrite this and make it better. template - struct ArrayRef { + struct ArrayRefT { using U32T = typename std::conditional::type; /* 00 */ U32T count; /* 04 */ U32T offset; /* 08 */ - } __attribute__((packed)); - struct ArrayRefLE : ArrayRef { - } __attribute__((packed)); - struct ArrayRefBE : ArrayRef { - } __attribute__((packed)); + } __packed__; + + using ArrayRef = ArrayRefT; + using ArrayRefBE = ArrayRefT; + check_struct_size(ArrayRef, 8); + check_struct_size(ArrayRefBE, 8); template - struct ItemBaseV2 { + struct ItemBaseV2T { using U32T = typename std::conditional::type; // id specifies several things; notably, it doubles as the index of the // item's name in the text archive (e.g. TextEnglish) collection 0. /* 00 */ U32T id = 0xFFFFFFFF; /* 04 */ - } __attribute__((packed)); + } __packed__; template - struct ItemBaseV3 : ItemBaseV2 { + struct ItemBaseV3T : ItemBaseV2T { using U16T = typename std::conditional::type; /* 04 */ U16T type = 0; /* 06 */ U16T skin = 0; /* 08 */ - } __attribute__((packed)); + } __packed__; template - struct ItemBaseV4 : ItemBaseV3 { + struct ItemBaseV4T : ItemBaseV3T { using U32T = typename std::conditional::type; /* 08 */ U32T team_points = 0; /* 0C */ - } __attribute__((packed)); + } __packed__; struct WeaponV4; struct WeaponDCProtos { - /* 00 */ ItemBaseV2 base; + /* 00 */ ItemBaseV2T base; /* 04 */ le_uint16_t class_flags = 0; /* 06 */ le_uint16_t atp_min = 0; /* 08 */ le_uint16_t atp_max = 0; @@ -68,10 +69,10 @@ public: /* 14 */ WeaponV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(WeaponDCProtos, 0x14); struct WeaponV1V2 { - /* 00 */ ItemBaseV2 base; + /* 00 */ ItemBaseV2T base; /* 04 */ le_uint16_t class_flags = 0; /* 06 */ le_uint16_t atp_min = 0; /* 08 */ le_uint16_t atp_max = 0; @@ -87,10 +88,10 @@ public: /* 18 */ WeaponV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(WeaponV1V2, 0x18); struct WeaponGCNTE { - /* 00 */ ItemBaseV3 base; + /* 00 */ ItemBaseV3T base; /* 08 */ be_uint16_t class_flags = 0; /* 0A */ be_uint16_t atp_min = 0; /* 0C */ be_uint16_t atp_max = 0; @@ -115,12 +116,12 @@ public: /* 24 */ WeaponV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(WeaponGCNTE, 0x24); template - struct WeaponV3 { + struct WeaponV3T { using U16T = typename std::conditional::type; - /* 00 */ ItemBaseV3 base; + /* 00 */ ItemBaseV3T base; /* 08 */ U16T class_flags = 0; /* 0A */ U16T atp_min = 0; /* 0C */ U16T atp_max = 0; @@ -149,10 +150,15 @@ public: /* 28 */ WeaponV4 to_v4() const; - } __attribute__((packed)); + } __packed__; + + using WeaponV3 = WeaponV3T; + using WeaponV3BE = WeaponV3T; + check_struct_size(WeaponV3, 0x28); + check_struct_size(WeaponV3BE, 0x28); struct WeaponV4 { - /* 00 */ ItemBaseV4 base; + /* 00 */ ItemBaseV4T base; /* 0C */ le_uint16_t class_flags = 0x00FF; /* 0E */ le_uint16_t atp_min = 0; /* 10 */ le_uint16_t atp_max = 0; @@ -179,10 +185,10 @@ public: /* 2A */ uint8_t tech_boost = 0; /* 2B */ uint8_t combo_type = 0; /* 2C */ - } __attribute__((packed)); + } __packed_ws__(WeaponV4, 0x2C); template - struct ArmorOrShield { + struct ArmorOrShieldT { using U16T = typename std::conditional::type; /* V1/V2 offsets */ /* 00 */ BaseT base; @@ -200,33 +206,36 @@ public: /* 12 */ uint8_t dfp_range = 0; /* 13 */ uint8_t evp_range = 0; /* 14 */ - } __attribute__((packed)); + } __packed__; - struct ArmorOrShieldV4; - struct ArmorOrShieldDCProtos : ArmorOrShield, false> { - ArmorOrShieldV4 to_v4() const; - } __attribute__((packed)); template - struct ArmorOrShieldFinal : ArmorOrShield { + struct ArmorOrShieldFinalT : ArmorOrShieldT { using U16T = typename std::conditional::type; /* 14 */ uint8_t stat_boost = 0; /* 15 */ uint8_t tech_boost = 0; /* 16 */ U16T unknown_a2 = 0; /* 18 */ - } __attribute__((packed)); + } __packed__; + using ArmorOrShieldV4 = ArmorOrShieldFinalT, false>; + check_struct_size(ArmorOrShieldV4, 0x20); + struct ArmorOrShieldDCProtos : ArmorOrShieldT, false> { + ArmorOrShieldV4 to_v4() const; + } __packed_ws__(ArmorOrShieldDCProtos, 0x14); - struct ArmorOrShieldV1V2 : ArmorOrShieldFinal, false> { + struct ArmorOrShieldV1V2 : ArmorOrShieldFinalT, false> { ArmorOrShieldV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(ArmorOrShieldV1V2, 0x18); template - struct ArmorOrShieldV3 : ArmorOrShieldFinal, IsBigEndian> { + struct ArmorOrShieldV3T : ArmorOrShieldFinalT, IsBigEndian> { ArmorOrShieldV4 to_v4() const; - } __attribute__((packed)); - struct ArmorOrShieldV4 : ArmorOrShieldFinal, false> { - } __attribute__((packed)); + } __packed__; + using ArmorOrShieldV3 = ArmorOrShieldV3T; + using ArmorOrShieldV3BE = ArmorOrShieldV3T; + check_struct_size(ArmorOrShieldV3, 0x1C); + check_struct_size(ArmorOrShieldV3BE, 0x1C); template - struct Unit { + struct UnitT { using U16T = typename std::conditional::type; using S16T = typename std::conditional::type; /* V1/V2 offsets */ @@ -234,31 +243,34 @@ public: /* 04 */ U16T stat = 0; /* 06 */ U16T stat_amount = 0; /* 08 */ - } __attribute__((packed)); + } __packed__; - struct UnitV4; - struct UnitDCProtos : Unit, false> { - UnitV4 to_v4() const; - } __attribute__((packed)); template - struct UnitFinal : Unit { + struct UnitFinalT : UnitT { using S16T = typename std::conditional::type; /* 08 */ S16T modifier_amount = 0; /* 0A */ parray unused; /* 0C */ - } __attribute__((packed)); - struct UnitV1V2 : UnitFinal, false> { + } __packed__; + using UnitV4 = UnitFinalT, false>; + check_struct_size(UnitV4, 0x14); + struct UnitDCProtos : UnitT, false> { UnitV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(UnitDCProtos, 0x08); + struct UnitV1V2 : UnitFinalT, false> { + UnitV4 to_v4() const; + } __packed_ws__(UnitV1V2, 0x0C); template - struct UnitV3 : UnitFinal, IsBigEndian> { + struct UnitV3T : UnitFinalT, IsBigEndian> { UnitV4 to_v4() const; - } __attribute__((packed)); - struct UnitV4 : UnitFinal, false> { - } __attribute__((packed)); + } __packed__; + using UnitV3 = UnitV3T; + using UnitV3BE = UnitV3T; + check_struct_size(UnitV3, 0x10); + check_struct_size(UnitV3BE, 0x10); template - struct Mag { + struct MagT { using U16T = typename std::conditional::type; /* V1/V2 offsets */ /* 00 */ BaseT base; @@ -289,36 +301,38 @@ public: /* 0E */ uint8_t on_death_flag = 0; /* 0F */ uint8_t on_boss_flag = 0; /* 10 */ - } __attribute__((packed)); + } __packed__; - struct MagV4; - struct MagV1 : Mag, false> { + struct MagV4 : MagT, false> { + le_uint16_t class_flags = 0x00FF; + parray unused; + } __packed_ws__(MagV4, 0x1C); + struct MagV1 : MagT, false> { MagV4 to_v4() const; - } __attribute__((packed)); - struct MagV2 : Mag, false> { + } __packed_ws__(MagV1, 0x10); + struct MagV2 : MagT, false> { /* 10 */ le_uint16_t class_flags = 0x00FF; /* 12 */ parray unused; /* 14 */ MagV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(MagV2, 0x14); template - struct MagV3 : Mag, IsBigEndian> { + struct MagV3T : MagT, IsBigEndian> { using U16T = typename std::conditional::type; /* 10 */ U16T class_flags = 0x00FF; /* 12 */ parray unused; /* 14 */ MagV4 to_v4() const; - } __attribute__((packed)); - struct MagV4 : Mag, false> { - /* 10 */ le_uint16_t class_flags = 0x00FF; - /* 12 */ parray unused; - /* 14 */ - } __attribute__((packed)); + } __packed__; + using MagV3 = MagV3T; + using MagV3BE = MagV3T; + check_struct_size(MagV3, 0x18); + check_struct_size(MagV3BE, 0x18); template - struct Tool { + struct ToolT { using U16T = typename std::conditional::type; using S32T = typename std::conditional::type; using U32T = typename std::conditional::type; @@ -329,18 +343,21 @@ public: /* 08 */ S32T cost = 0; /* 0C */ U32T item_flag = 0; /* 10 */ - } __attribute__((packed)); + } __packed__; - struct ToolV4; - struct ToolV1V2 : Tool, false> { + using ToolV4 = ToolT, false>; + check_struct_size(ToolV4, 0x18); + struct ToolV1V2 : ToolT, false> { ToolV4 to_v4() const; - } __attribute__((packed)); + } __packed_ws__(ToolV1V2, 0x10); template - struct ToolV3 : Tool, IsBigEndian> { + struct ToolV3T : ToolT, IsBigEndian> { ToolV4 to_v4() const; - } __attribute__((packed)); - struct ToolV4 : Tool, false> { - } __attribute__((packed)); + } __packed__; + using ToolV3 = ToolV3T; + using ToolV3BE = ToolV3T; + check_struct_size(ToolV3, 0x14); + check_struct_size(ToolV3BE, 0x14); struct MagFeedResult { int8_t def = 0; @@ -350,31 +367,43 @@ public: int8_t iq = 0; int8_t synchro = 0; parray unused; - } __attribute__((packed)); + } __packed_ws__(MagFeedResult, 8); using MagFeedResultsList = parray; template - struct MagFeedResultsListOffsets { + struct MagFeedResultsListOffsetsT { using U32T = typename std::conditional::type; parray offsets; // Offsets of MagFeedResultsList objects - } __attribute__((packed)); + } __packed__; + using MagFeedResultsListOffsets = MagFeedResultsListOffsetsT; + using MagFeedResultsListOffsetsBE = MagFeedResultsListOffsetsT; + check_struct_size(MagFeedResultsListOffsets, 0x20); + check_struct_size(MagFeedResultsListOffsetsBE, 0x20); template - struct Special { + struct SpecialT { using U16T = typename std::conditional::type; U16T type = 0xFFFF; U16T amount = 0; - } __attribute__((packed)); + } __packed__; + using Special = SpecialT; + using SpecialBE = SpecialT; + check_struct_size(Special, 4); + check_struct_size(SpecialBE, 4); template - struct StatBoost { + struct StatBoostT { using U16T = typename std::conditional::type; uint8_t stat1 = 0; uint8_t stat2 = 0; U16T amount1 = 0; U16T amount2 = 0; - } __attribute__((packed)); + } __packed__; + using StatBoost = StatBoostT; + using StatBoostBE = StatBoostT; + check_struct_size(StatBoost, 6); + check_struct_size(StatBoostBE, 6); // Indexed as [tech_num][char_class] using MaxTechniqueLevels = parray, 19>; @@ -388,10 +417,10 @@ public: uint8_t level = 0; uint8_t char_class = 0; parray unused; - } __attribute__((packed)); + } __packed_ws__(ItemCombination, 0x10); template - struct TechniqueBoost { + struct TechniqueBoostT { using U32T = typename std::conditional::type; using FloatT = typename std::conditional::type; U32T tech1 = 0; @@ -400,26 +429,34 @@ public: FloatT boost2 = 0.0f; U32T tech3 = 0; FloatT boost3 = 0.0f; - } __attribute__((packed)); + } __packed__; + using TechniqueBoost = TechniqueBoostT; + using TechniqueBoostBE = TechniqueBoostT; + check_struct_size(TechniqueBoost, 0x18); + check_struct_size(TechniqueBoostBE, 0x18); struct EventItem { parray item; uint8_t probability = 0; - } __attribute__((packed)); + } __packed_ws__(EventItem, 4); struct UnsealableItem { parray item; uint8_t unused = 0; - } __attribute__((packed)); + } __packed_ws__(UnsealableItem, 4); template - struct NonWeaponSaleDivisors { + struct NonWeaponSaleDivisorsT { using FloatT = typename std::conditional::type; FloatT armor_divisor = 0.0f; FloatT shield_divisor = 0.0f; FloatT unit_divisor = 0.0f; FloatT mag_divisor = 0.0f; - } __attribute__((packed)); + } __packed__; + using NonWeaponSaleDivisors = NonWeaponSaleDivisorsT; + using NonWeaponSaleDivisorsBE = NonWeaponSaleDivisorsT; + check_struct_size(NonWeaponSaleDivisors, 0x10); + check_struct_size(NonWeaponSaleDivisorsBE, 0x10); ItemParameterTable(std::shared_ptr data, Version version); ~ItemParameterTable() = default; @@ -443,7 +480,7 @@ public: const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const; uint8_t get_item_stars(uint32_t id) const; uint8_t get_special_stars(uint8_t special) const; - const Special& get_special(uint8_t special) const; + const Special& get_special(uint8_t special) const; uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const; uint8_t get_weapon_v1_replacement(uint8_t data1_1) const; @@ -493,7 +530,7 @@ private: /* 44 / 0668 / 0668 */ le_uint32_t unknown_a2; /* 48 / 030C / 030C */ le_uint32_t unknown_a3; /* 4C / 2CE4 / 2D78 */ le_uint32_t unknown_a4; - } __attribute__((packed)); + } __packed_ws__(TableOffsetsDCProtos, 0x50); struct TableOffsetsV1V2 { // TODO: Is weapon count 0x89 or 0x8A? It could be that the last entry in @@ -516,7 +553,7 @@ private: /* 38 / 2C12 / 4540 */ le_uint32_t special_data_table; // -> [Special](0x29) /* 3C / 2CB8 / 58DC */ le_uint32_t stat_boost_table; // -> [StatBoost] /* 40 / 3198 / 5704 */ le_uint32_t shield_effect_table; // -> [8-byte structs] - } __attribute__((packed)); + } __packed_ws__(TableOffsetsV1V2, 0x44); struct TableOffsetsGCNTE { /* 00 / 6F0C */ be_uint32_t weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED) @@ -539,10 +576,10 @@ private: /* 44 / 737C */ be_uint32_t combination_table; // -> {count, offset -> [ItemCombination]} /* 48 / 68B0 */ be_uint32_t unknown_a1; /* 4C / 6B1C */ be_uint32_t tech_boost_table; // -> [TechniqueBoost] (always 0x2C of them? from counts struct?) - } __attribute__((packed)); + } __packed_ws__(TableOffsetsGCNTE, 0x50); template - struct TableOffsetsV3V4 { + struct TableOffsetsV3V4T { using U32T = typename std::conditional::type; /* ## / GC / BB */ /* 00 / F078 / 14884 */ U32T weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED) @@ -568,7 +605,11 @@ private: /* 50 / F5F0 / 15014 */ U32T unwrap_table; // -> {count, offset -> [{count, offset -> [EventItem]}]} /* 54 / F5F8 / 1501C */ U32T unsealable_table; // -> {count, offset -> [UnsealableItem]} /* 58 / F600 / 15024 */ U32T ranged_special_table; // -> {count, offset -> [4-byte structs]} - } __attribute__((packed)); + } __packed__; + using TableOffsetsV3V4 = TableOffsetsV3V4T; + using TableOffsetsV3V4BE = TableOffsetsV3V4T; + check_struct_size(TableOffsetsV3V4, 0x5C); + check_struct_size(TableOffsetsV3V4BE, 0x5C); Version version; std::shared_ptr data; @@ -576,9 +617,9 @@ private: const TableOffsetsDCProtos* offsets_dc_protos; const TableOffsetsV1V2* offsets_v1_v2; const TableOffsetsGCNTE* offsets_gc_nte; - const TableOffsetsV3V4* offsets_v3_le; - const TableOffsetsV3V4* offsets_v3_be; - const TableOffsetsV3V4* offsets_v4; + const TableOffsetsV3V4* offsets_v3_le; + const TableOffsetsV3V4BE* offsets_v3_be; + const TableOffsetsV3V4* offsets_v4; // These are unused if offsets_v4 is not null (in that case, we just return // references pointing inside the data string) @@ -588,13 +629,13 @@ private: mutable std::vector parsed_units; mutable std::vector parsed_mags; mutable std::unordered_map parsed_tools; - mutable std::vector> parsed_specials; + mutable std::vector parsed_specials; // Key is used_item. We can't index on (used_item, equipped_item) because // equipped_item may contain wildcards, and the matching order matters. mutable std::map> item_combination_index; - template + template std::pair find_tool_by_id_t(uint32_t tool_table_offset, uint32_t id) const; template float get_sale_divisor_t(const OffsetsT* offsets, uint8_t data1_0, uint8_t data1_1) const; @@ -614,11 +655,11 @@ public: /* 0C / 0556 */ le_uint32_t unknown_a4; // -> (uint8_t)[num_mags] /* 10 / 05AC */ le_uint32_t unknown_a5; // -> (float)[0x48] /* 14 / 06CC */ le_uint32_t evolution_number; // -> (uint8_t)[num_mags] - } __attribute__((packed)); + } __packed_ws__(TableOffsets, 0x18); struct EvolutionNumberTable { parray values; - } __attribute__((packed)); + } __packed_ws__(EvolutionNumberTable, 0x53); MagEvolutionTable(std::shared_ptr data); ~MagEvolutionTable() = default; diff --git a/src/LevelTable.cc b/src/LevelTable.cc index 82e73bbc..500af596 100644 --- a/src/LevelTable.cc +++ b/src/LevelTable.cc @@ -9,26 +9,26 @@ using namespace std; -void PlayerStats::reset_to_base(uint8_t char_class, shared_ptr level_table) { - this->level = 0; - this->experience = 0; - this->char_stats = level_table->base_stats_for_class(char_class); +void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const { + stats.level = 0; + stats.experience = 0; + stats.char_stats = this->base_stats_for_class(char_class); } -void PlayerStats::advance_to_level(uint8_t char_class, uint32_t level, shared_ptr level_table) { - for (; this->level < level; this->level++) { - const auto& level_stats = level_table->stats_delta_for_level(char_class, this->level + 1); +void LevelTable::advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const { + for (; stats.level < level; stats.level++) { + const auto& level_stats = this->stats_delta_for_level(char_class, stats.level + 1); // The original code clamps the resulting stat values to [0, max_stat]; we // don't have max_stat handy so we just allow them to be unbounded - this->char_stats.atp += level_stats.atp; - this->char_stats.mst += level_stats.mst; - this->char_stats.evp += level_stats.evp; - this->char_stats.hp += level_stats.hp; - this->char_stats.dfp += level_stats.dfp; - this->char_stats.ata += level_stats.ata; + stats.char_stats.atp += level_stats.atp; + stats.char_stats.mst += level_stats.mst; + stats.char_stats.evp += level_stats.evp; + stats.char_stats.hp += level_stats.hp; + stats.char_stats.dfp += level_stats.dfp; + stats.char_stats.ata += level_stats.ata; // Note: It is not a bug that lck is ignored here; the original code // ignores it too. - this->experience = level_stats.experience; + stats.experience = level_stats.experience; } } @@ -52,7 +52,7 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) { le_uint32_t unknown_a10; // -> u32[3] -> (0x10-byte struct)[0x0C] le_uint32_t unknown_a11; // -> u32[3] -> (0x30-bytes) le_uint32_t unknown_a12; // -> u32[3] -> (0x14-byte struct)[0x0F] - } __attribute__((packed)); + } __packed_ws__(Offsets, 0x40); StringReader r; string decompressed_data; @@ -158,7 +158,7 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) { struct Offsets { le_uint32_t base_stats; // -> u32[12] -> CharacterStats le_uint32_t level_deltas; // -> u32[12] -> LevelStatsDelta[200] - } __attribute__((packed)); + } __packed_ws__(Offsets, 8); StringReader r; string decompressed_data; diff --git a/src/LevelTable.hh b/src/LevelTable.hh index 0b076093..c9286853 100644 --- a/src/LevelTable.hh +++ b/src/LevelTable.hh @@ -7,35 +7,74 @@ #include #include +#include "Text.hh" + class LevelTable; -struct CharacterStats { - /* 00 */ le_uint16_t atp = 0; - /* 02 */ le_uint16_t mst = 0; - /* 04 */ le_uint16_t evp = 0; - /* 06 */ le_uint16_t hp = 0; - /* 08 */ le_uint16_t dfp = 0; - /* 0A */ le_uint16_t ata = 0; - /* 0C */ le_uint16_t lck = 0; +template +struct CharacterStatsT { + using U16T = typename std::conditional::type; + + /* 00 */ U16T atp = 0; + /* 02 */ U16T mst = 0; + /* 04 */ U16T evp = 0; + /* 06 */ U16T hp = 0; + /* 08 */ U16T dfp = 0; + /* 0A */ U16T ata = 0; + /* 0C */ U16T lck = 0; /* 0E */ -} __attribute__((packed)); -struct PlayerStats { - /* 00 */ CharacterStats char_stats; - /* 0E */ le_uint16_t esp = 0; - /* 10 */ le_float height = 0.0; - /* 14 */ le_float unknown_a3 = 0.0; - /* 18 */ le_uint32_t level = 0; - /* 1C */ le_uint32_t experience = 0; - /* 20 */ le_uint32_t meseta = 0; - /* 24 */ - - void reset_to_base(uint8_t char_class, std::shared_ptr level_table); - void advance_to_level(uint8_t char_class, uint32_t level, std::shared_ptr level_table); -} __attribute__((packed)); + operator CharacterStatsT() const { + CharacterStatsT ret; + ret.atp = this->atp.load(); + ret.mst = this->mst.load(); + ret.evp = this->evp.load(); + ret.hp = this->hp.load(); + ret.dfp = this->dfp.load(); + ret.ata = this->ata.load(); + ret.lck = this->lck.load(); + return ret; + } +} __packed__; +using CharacterStats = CharacterStatsT; +using CharacterStatsBE = CharacterStatsT; +check_struct_size(CharacterStats, 0x0E); +check_struct_size(CharacterStatsBE, 0x0E); template -struct LevelStatsDeltaBase { +struct PlayerStatsT { + using U16T = typename std::conditional::type; + using U32T = typename std::conditional::type; + using F32T = typename std::conditional::type; + + /* 00 */ CharacterStatsT char_stats; + /* 0E */ U16T esp = 0; + /* 10 */ F32T height = 0.0; + /* 14 */ F32T unknown_a3 = 0.0; + /* 18 */ U32T level = 0; + /* 1C */ U32T experience = 0; + /* 20 */ U32T meseta = 0; + /* 24 */ + + operator PlayerStatsT() const { + PlayerStatsT ret; + ret.char_stats = this->char_stats; + ret.esp = this->esp.load(); + ret.height = this->height.load(); + ret.unknown_a3 = this->unknown_a3.load(); + ret.level = this->level.load(); + ret.experience = this->experience.load(); + ret.meseta = this->meseta.load(); + return ret; + } +} __packed__; +using PlayerStats = PlayerStatsT; +using PlayerStatsBE = PlayerStatsT; +check_struct_size(PlayerStats, 0x24); +check_struct_size(PlayerStatsBE, 0x24); + +template +struct LevelStatsDeltaT { using U32T = typename std::conditional::type; /* 00 */ uint8_t atp; @@ -58,12 +97,11 @@ struct LevelStatsDeltaBase { ps.mst += this->mst; ps.lck += this->lck; } -} __attribute__((packed)); - -struct LevelStatsDelta : LevelStatsDeltaBase { -} __attribute__((packed)); -struct LevelStatsDeltaBE : LevelStatsDeltaBase { -} __attribute__((packed)); +} __packed__; +using LevelStatsDelta = LevelStatsDeltaT; +using LevelStatsDeltaBE = LevelStatsDeltaT; +check_struct_size(LevelStatsDelta, 0x0C); +check_struct_size(LevelStatsDeltaBE, 0x0C); class LevelTable { // This is the base class for all the LevelTable implementations. The public @@ -76,6 +114,9 @@ public: virtual const CharacterStats& base_stats_for_class(uint8_t char_class) const = 0; virtual const LevelStatsDelta& stats_delta_for_level(uint8_t char_class, uint8_t level) const = 0; + void reset_to_base(PlayerStats& stats, uint8_t char_class) const; + void advance_to_level(PlayerStats& stats, uint32_t level, uint8_t char_class) const; + protected: LevelTable() = default; }; @@ -89,7 +130,7 @@ public: /* 14 */ le_float unknown_a3 = 0.0; /* 18 */ le_uint32_t level = 0; /* 1C */ - } __attribute__((packed)); + } __packed_ws__(Level100Entry, 0x1C); LevelTableV2(const std::string& data, bool compressed); virtual ~LevelTableV2() = default; diff --git a/src/License.cc b/src/License.cc index 01bf0f77..c90b2013 100644 --- a/src/License.cc +++ b/src/License.cc @@ -347,7 +347,7 @@ DiskLicenseIndex::DiskLicenseIndex() { pstring gc_password; // GC password uint32_t privileges; // privilege level uint64_t ban_end_time; // end time of ban (zero = not banned) - } __attribute__((packed)); + } __packed_ws__(BinaryLicense, 0x54); if (!isdir("system/licenses")) { mkdir("system/licenses", 0755); diff --git a/src/Lobby.cc b/src/Lobby.cc index 811d70f7..b096c108 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -650,7 +650,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { this->battle_record->add_player( lobby_data, p->inventory, - p->disp.to_dcpcv3(c->language(), c->language()), + p->disp.to_dcpcv3(c->language(), c->language()), c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0); } diff --git a/src/Map.cc b/src/Map.cc index ff8a7b70..332bb3d7 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -1940,7 +1940,8 @@ void SetDataTable::load_table_t(const string& data) { U32T unknown_a6; // == 0 U32T unknown_a7; // == 0 U32T unknown_a8; // == 0 - } __attribute__((packed)); + } __packed_ws__(Footer, 0x20); + if (r.size() < sizeof(Footer)) { throw runtime_error("set data table is too small"); } diff --git a/src/Map.hh b/src/Map.hh index 375e7546..121d7735 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -34,7 +34,7 @@ struct Map { inline Type type() const { return static_cast(this->le_type.load()); } - } __attribute__((packed)); + } __packed_ws__(SectionHeader, 0x10); struct ObjectEntry { // Section type 1 (OBJECTS) /* 00 */ le_uint16_t base_type; @@ -61,7 +61,7 @@ struct Map { /* 44 */ std::string str() const; - } __attribute__((packed)); + } __packed_ws__(ObjectEntry, 0x44); struct EnemyEntry { // Section type 2 (ENEMIES) /* 00 */ le_uint16_t base_type; @@ -91,7 +91,7 @@ struct Map { /* 48 */ std::string str() const; - } __attribute__((packed)); + } __packed_ws__(EnemyEntry, 0x48); struct EventsSectionHeader { // Section type 3 (WAVE_EVENTS) /* 00 */ le_uint32_t action_stream_offset; @@ -99,7 +99,7 @@ struct Map { /* 08 */ le_uint32_t entry_count; /* 0C */ be_uint32_t format; // 0 or 'evt2' /* 10 */ - } __attribute__((packed)); + } __packed_ws__(EventsSectionHeader, 0x10); struct Event1Entry { // Section type 3 (WAVE_EVENTS) if format == 0 /* 00 */ le_uint32_t event_id; @@ -114,7 +114,7 @@ struct Map { /* 0C */ le_uint32_t delay; /* 10 */ le_uint32_t action_stream_offset; /* 14 */ - } __attribute__((packed)); + } __packed_ws__(Event1Entry, 0x14); struct Event2Entry { // Section type 3 (WAVE_EVENTS) if format == 'evt2' /* 00 */ le_uint32_t event_id; @@ -129,21 +129,21 @@ struct Map { /* 12 */ le_uint16_t max_waves; /* 14 */ le_uint32_t action_stream_offset; /* 18 */ - } __attribute__((packed)); + } __packed_ws__(Event2Entry, 0x18); struct RandomEnemyLocationsHeader { // Section type 4 (RANDOM_ENEMY_LOCATIONS) /* 00 */ le_uint32_t section_table_offset; // Offset to RandomEnemyLocationSegment structs, from start of this struct /* 04 */ le_uint32_t entries_offset; // Offset to RandomEnemyLocationEntry structs, from start of this struct /* 08 */ le_uint32_t num_sections; /* 0C */ - } __attribute__((packed)); + } __packed_ws__(RandomEnemyLocationsHeader, 0x0C); struct RandomEnemyLocationSection { // Section type 4 (RANDOM_ENEMY_LOCATIONS) /* 00 */ le_uint16_t section; /* 02 */ le_uint16_t count; /* 04 */ le_uint32_t offset; /* 08 */ - } __attribute__((packed)); + } __packed_ws__(RandomEnemyLocationSection, 8); struct RandomEnemyLocationEntry { // Section type 4 (RANDOM_ENEMY_LOCATIONS) /* 00 */ le_float x; @@ -155,7 +155,7 @@ struct Map { /* 18 */ uint16_t unknown_a9; /* 1A */ uint16_t unknown_a10; /* 1C */ - } __attribute__((packed)); + } __packed_ws__(RandomEnemyLocationEntry, 0x1C); struct RandomEnemyDefinitionsHeader { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) /* 00 */ le_uint32_t entries_offset; // Offset to RandomEnemyDefinition structs, from start of this struct @@ -163,7 +163,7 @@ struct Map { /* 08 */ le_uint32_t entry_count; /* 0C */ le_uint32_t weight_entry_count; /* 10 */ - } __attribute__((packed)); + } __packed_ws__(RandomEnemyDefinitionsHeader, 0x10); struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) // All fields through entry_num map to the corresponding fields in @@ -179,7 +179,7 @@ struct Map { /* 1C */ le_uint16_t min_children; /* 1E */ le_uint16_t max_children; /* 20 */ - } __attribute__((packed)); + } __packed_ws__(RandomEnemyDefinition, 0x20); struct RandomEnemyWeight { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) /* 00 */ uint8_t base_type_index; @@ -187,7 +187,7 @@ struct Map { /* 02 */ uint8_t weight; /* 03 */ uint8_t unknown_a4; /* 04 */ - } __attribute__((packed)); + } __packed_ws__(RandomEnemyWeight, 4); struct RareEnemyRates { uint32_t hildeblue; // HILDEBEAR -> HILDEBLUE diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index 8e74addb..f3b4049e 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -114,13 +114,13 @@ public: parray as32; InitialKeys() : as32() {} InitialKeys(const InitialKeys& other) : as32(other.as32) {} - } __attribute__((packed)); + } __packed_ws__(InitialKeys, 0x48); union PrivateKeys { parray as8; parray as32; PrivateKeys() : as32() {} PrivateKeys(const PrivateKeys& other) : as32(other.as32) {} - } __attribute__((packed)); + } __packed_ws__(PrivateKeys, 0x1000); InitialKeys initial_keys; PrivateKeys private_keys; // This field only really needs to be one byte, but annoyingly, some @@ -129,7 +129,7 @@ public: // this structure's size from not matching the .nsk files' sizes, we use // an unnecessarily large size for this field. le_uint64_t subtype; - } __attribute__((packed)); + } __packed_ws__(KeyFile, 0x1050); PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size); @@ -254,39 +254,49 @@ uint32_t encrypt_challenge_time(uint16_t value); uint16_t decrypt_challenge_time(uint32_t value); template -class ChallengeTime { +class ChallengeTimeT { private: using U32T = typename std::conditional::type; U32T value; public: - ChallengeTime() = default; - ChallengeTime(uint16_t v) { - this->store(v); + ChallengeTimeT() = default; + ChallengeTimeT(uint16_t v) { + this->encode(v); } - ChallengeTime(const ChallengeTime& other) = default; - ChallengeTime(ChallengeTime&& other) = default; - ChallengeTime& operator=(const ChallengeTime& other) = default; - ChallengeTime& operator=(ChallengeTime&& other) = default; + ChallengeTimeT(const ChallengeTimeT& other) = default; + ChallengeTimeT(ChallengeTimeT&& other) = default; + ChallengeTimeT& operator=(const ChallengeTimeT& other) = default; + ChallengeTimeT& operator=(ChallengeTimeT&& other) = default; bool has_value() const { return this->value != 0; } - uint16_t load() const { + uint32_t load_raw() const { + return this->value; + } + void store_raw(uint32_t value) { + this->value = value; + } + + uint16_t decode() const { return decrypt_challenge_time(this->value); } - operator uint16_t() const { - return this->load(); - } - void store(uint16_t v) { + void encode(uint16_t v) { this->value = ((v == 0) || (v == 0xFFFF)) ? 0 : encrypt_challenge_time(v); } - ChallengeTime& operator=(uint16_t v) { - this->store(v); - return *this; + + operator ChallengeTimeT() const { + ChallengeTimeT ret; + ret.store_raw(this->value); + return ret; } -} __attribute__((packed)); +} __packed__; +using ChallengeTime = ChallengeTimeT; +using ChallengeTimeBE = ChallengeTimeT; +check_struct_size(ChallengeTime, 4); +check_struct_size(ChallengeTimeBE, 4); std::string decrypt_v2_registry_value(const void* data, size_t size); diff --git a/src/PSOGCObjectGraph.cc b/src/PSOGCObjectGraph.cc index 0fa90208..4290a0b3 100644 --- a/src/PSOGCObjectGraph.cc +++ b/src/PSOGCObjectGraph.cc @@ -11,7 +11,7 @@ struct TObjectVTable { be_uint32_t update; be_uint32_t render; be_uint32_t render_shadow; -} __attribute__((packed)); +} __packed_ws__(TObjectVTable, 0x18); struct TObject { be_uint32_t type_name_addr; @@ -22,7 +22,7 @@ struct TObject { be_uint32_t parent_addr; be_uint32_t children_head_addr; be_uint32_t vtable_addr; -} __attribute__((packed)); +} __packed_ws__(TObject, 0x1C); PSOGCObjectGraph::PSOGCObjectGraph( const string& memory_data, uint32_t root_address) { diff --git a/src/PSOProtocol.hh b/src/PSOProtocol.hh index 96af4569..41b1f0c1 100644 --- a/src/PSOProtocol.hh +++ b/src/PSOProtocol.hh @@ -13,19 +13,19 @@ struct PSOCommandHeaderPC { le_uint16_t size; uint8_t command; uint8_t flag; -} __attribute__((packed)); +} __packed_ws__(PSOCommandHeaderPC, 4); struct PSOCommandHeaderDCV3 { uint8_t command; uint8_t flag; le_uint16_t size; -} __attribute__((packed)); +} __packed_ws__(PSOCommandHeaderDCV3, 4); struct PSOCommandHeaderBB { le_uint16_t size; le_uint16_t command; le_uint32_t flag; -} __attribute__((packed)); +} __packed_ws__(PSOCommandHeaderBB, 8); union PSOCommandHeader { PSOCommandHeaderDCV3 dc; @@ -45,7 +45,7 @@ union PSOCommandHeader { } PSOCommandHeader(); -} __attribute__((packed)); +} __packed_ws__(PSOCommandHeader, 8); // This function is used in a lot of places to check received command sizes and // cast them to the appropriate type diff --git a/src/PlayerInventory.hh b/src/PlayerInventory.hh new file mode 100644 index 00000000..20dd48e7 --- /dev/null +++ b/src/PlayerInventory.hh @@ -0,0 +1,407 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ChoiceSearch.hh" +#include "FileContentsCache.hh" +#include "ItemData.hh" +#include "PSOEncryption.hh" +#include "Text.hh" +#include "Version.hh" + +class Client; +class ItemParameterTable; + +// PSO V2 stored some extra data in the character structs in a format that I'm +// sure Sega thought was very clever for backward compatibility, but for us is +// just plain annoying. Specifically, they used the third and fourth bytes of +// the InventoryItem struct to store some things not present in V1. The game +// stores arrays of bytes striped across these structures. In newserv, we call +// those fields extension_data. They contain: +// items[0].extension_data1 through items[19].extension_data1: +// Extended technique levels. The values in the technique_levels_v1 array +// only go up to 14 (tech level 15); if the player has a technique above +// level 15, the corresponding extension_data1 field holds the remaining +// levels (so a level 20 tech would have 14 in technique_levels_v1 and 5 +// in the corresponding item's extension_data1 field). +// items[0].extension_data2 through items[3].extension_data2: +// The flags field from the PSOGCCharacterFile::Character struct; see +// SaveFileFormats.hh for details. +// items[4].extension_data2 through items[7].extension_data2: +// The timestamp when the character was last saved, in seconds since +// January 1, 2000. Stored little-endian, so items[4] contains the LSB. +// items[8].extension_data2 through items[12].extension_data2: +// Number of power materials, mind materials, evade materials, def +// materials, and luck materials (respectively) used by the player. +// items[13].extension_data2 through items[15].extension_data2: +// Unknown. These are not an array, but do appear to be related. + +template +struct PlayerInventoryItemT { + /* 00 */ uint8_t present = 0; + /* 01 */ uint8_t unknown_a1 = 0; + // See note above about these fields + /* 02 */ uint8_t extension_data1 = 0; + /* 03 */ uint8_t extension_data2 = 0; + /* 04 */ le_uint32_t flags = 0; // 8 = equipped + /* 08 */ ItemData data; + /* 1C */ + + PlayerInventoryItemT() = default; + + PlayerInventoryItemT(const ItemData& item, bool equipped) + : present(1), + unknown_a1(0), + extension_data1(0), + extension_data2(0), + flags(equipped ? 8 : 0), + data(item) {} + + operator PlayerInventoryItemT() const { + PlayerInventoryItemT ret; + ret.present = this->present; + ret.unknown_a1 = this->unknown_a1; + ret.extension_data1 = this->extension_data1; + ret.extension_data2 = this->extension_data2; + ret.flags = this->flags.load(); + ret.data = this->data; + return ret; + } +} __packed__; +using PlayerInventoryItem = PlayerInventoryItemT; +using PlayerInventoryItemBE = PlayerInventoryItemT; +check_struct_size(PlayerInventoryItem, 0x1C); +check_struct_size(PlayerInventoryItemBE, 0x1C); + +template +struct PlayerBankItemT { + using U16T = typename std::conditional::type; + + /* 00 */ ItemData data; + /* 14 */ U16T amount = 0; + /* 16 */ U16T present = 0; + /* 18 */ + + inline bool operator<(const PlayerBankItemT& other) const { + return this->data < other.data; + } + + operator PlayerBankItemT() const { + PlayerBankItemT ret; + ret.data = this->data; + ret.amount = this->amount.load(); + ret.present = this->present.load(); + return ret; + } +} __packed__; +using PlayerBankItem = PlayerBankItemT; +using PlayerBankItemBE = PlayerBankItemT; +check_struct_size(PlayerBankItem, 0x18); +check_struct_size(PlayerBankItemBE, 0x18); + +template +struct PlayerInventoryT { + /* 0000 */ uint8_t num_items = 0; + /* 0001 */ uint8_t hp_from_materials = 0; + /* 0002 */ uint8_t tp_from_materials = 0; + /* 0003 */ uint8_t language = 0; + /* 0004 */ parray, 30> items; + /* 034C */ + + size_t find_item(uint32_t item_id) const { + for (size_t x = 0; x < this->num_items; x++) { + if (this->items[x].data.id == item_id) { + return x; + } + } + throw std::out_of_range("item not present"); + } + + size_t find_item_by_primary_identifier(uint32_t primary_identifier) const { + for (size_t x = 0; x < this->num_items; x++) { + if (this->items[x].data.primary_identifier() == primary_identifier) { + return x; + } + } + throw std::out_of_range("item not present"); + } + + size_t find_equipped_item(EquipSlot slot) const { + ssize_t ret = -1; + for (size_t y = 0; y < this->num_items; y++) { + const auto& i = this->items[y]; + if (!(i.flags & 0x00000008)) { + continue; + } + if (!i.data.can_be_equipped_in_slot(slot)) { + continue; + } + + // Units can be equipped in multiple slots, so the currently-equipped slot + // is stored in the item data itself. + if (((slot == EquipSlot::UNIT_1) && (i.data.data1[4] != 0x00)) || + ((slot == EquipSlot::UNIT_2) && (i.data.data1[4] != 0x01)) || + ((slot == EquipSlot::UNIT_3) && (i.data.data1[4] != 0x02)) || + ((slot == EquipSlot::UNIT_4) && (i.data.data1[4] != 0x03))) { + continue; + } + + if (ret < 0) { + ret = y; + } else { + throw std::runtime_error("multiple items are equipped in the same slot"); + } + } + if (ret < 0) { + throw std::out_of_range("no item is equipped in this slot"); + } + return ret; + } + + bool has_equipped_item(EquipSlot slot) const { + try { + this->find_equipped_item(slot); + return true; + } catch (const std::out_of_range&) { + return false; + } + } + + void equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite) { + this->equip_item_index(this->find_item(item_id), slot, allow_overwrite); + } + + void equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite) { + auto& item = this->items[index]; + + if ((slot == EquipSlot::UNKNOWN) || !item.data.can_be_equipped_in_slot(slot)) { + slot = item.data.default_equip_slot(); + } + + if (this->has_equipped_item(slot)) { + if (allow_overwrite) { + this->unequip_item_slot(slot); + } else { + throw std::runtime_error("equip slot is already in use"); + } + } + + item.flags |= 0x00000008; + // Units store which slot they're equipped in within the item data itself + if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) { + item.data.data1[4] = static_cast(slot) - 9; + } + } + + void unequip_item_id(uint32_t item_id) { + this->unequip_item_index(this->find_item(item_id)); + } + + void unequip_item_slot(EquipSlot slot) { + this->unequip_item_index(this->find_equipped_item(slot)); + } + + void unequip_item_index(size_t index) { + auto& item = this->items[index]; + + item.flags &= (~0x00000008); + // Units store which slot they're equipped in within the item data itself + if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) { + item.data.data1[4] = 0x00; + } + // If the item is an armor, remove all units too + if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x01)) { + for (size_t z = 0; z < 30; z++) { + auto& unit = this->items[z]; + if ((unit.data.data1[0] == 0x01) && (unit.data.data1[1] == 0x03)) { + unit.flags &= (~0x00000008); + unit.data.data1[4] = 0x00; + } + } + } + } + + 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) && + ((data1_1 < 0) || (this->items[read_offset].data.data1[1] == static_cast(data1_1)))); + if (!should_delete) { + if (read_offset != write_offset) { + this->items[write_offset].present = this->items[read_offset].present; + this->items[write_offset].unknown_a1 = this->items[read_offset].unknown_a1; + this->items[write_offset].flags = this->items[read_offset].flags; + this->items[write_offset].data = this->items[read_offset].data; + } + write_offset++; + } + } + size_t ret = this->num_items - write_offset; + this->num_items = write_offset; + return ret; + } + + void decode_from_client(Version v) { + for (size_t z = 0; z < this->items.size(); z++) { + this->items[z].data.decode_for_version(v); + } + } + + void encode_for_client(Version v, std::shared_ptr item_parameter_table) { + if (v == Version::DC_NTE) { + // DC NTE has the item count as a 32-bit value here, whereas every other + // version uses a single byte. To stop DC NTE from crashing by trying to + // construct far more than 30 TItem objects, we clear the fields DC NTE + // doesn't know about. Note that the 11/2000 prototype does not have this + // issue - its inventory format matches the rest of the versions. + this->hp_from_materials = 0; + this->tp_from_materials = 0; + this->language = 0; + } else if ((v != Version::PC_NTE) && (v != Version::PC_V2)) { + if (this->language > 4) { + this->language = 0; + } + } else { + if (this->language > 7) { + this->language = 0; + } + } + + // For pre-V2 clients, use the V2 parameter table, since the V1 table + // doesn't have correct encodings for backward-compatible V2 items. + for (size_t z = 0; z < this->items.size(); z++) { + this->items[z].data.encode_for_version(v, item_parameter_table); + } + } + + operator PlayerInventoryT() const { + PlayerInventoryT ret; + ret.num_items = this->num_items; + ret.hp_from_materials = this->hp_from_materials; + ret.tp_from_materials = this->tp_from_materials; + ret.language = this->language; + ret.items = this->items; + return ret; + } +} __packed__; +using PlayerInventory = PlayerInventoryT; +using PlayerInventoryBE = PlayerInventoryT; +check_struct_size(PlayerInventory, 0x34C); +check_struct_size(PlayerInventoryBE, 0x34C); + +template +struct PlayerBankT { + using U32T = typename std::conditional::type; + + /* 0000 */ U32T num_items = 0; + /* 0004 */ U32T meseta = 0; + /* 0008 */ parray, 200> items; + /* 12C8 */ + + void add_item(const ItemData& item, const ItemData::StackLimits& limits) { + uint32_t primary_identifier = item.primary_identifier(); + + if (primary_identifier == 0x04000000) { + this->meseta += item.data2d; + if (this->meseta > 999999) { + this->meseta = 999999; + } + return; + } + + size_t combine_max = item.max_stack_size(limits); + if (combine_max > 1) { + size_t y; + for (y = 0; y < this->num_items; y++) { + if (this->items[y].data.primary_identifier() == primary_identifier) { + break; + } + } + + if (y < this->num_items) { + uint8_t new_count = this->items[y].data.data1[5] + item.data1[5]; + if (new_count > combine_max) { + throw std::runtime_error("stack size would exceed limit"); + } + this->items[y].data.data1[5] = new_count; + this->items[y].amount = new_count; + return; + } + } + + if (this->num_items >= 200) { + throw std::runtime_error("no free space in bank"); + } + auto& last_item = this->items[this->num_items]; + last_item.data = item; + last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1; + last_item.present = 1; + this->num_items++; + } + + 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]; + + ItemData ret; + if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) { + ret = bank_item.data; + ret.data1[5] = amount; + bank_item.data.data1[5] -= amount; + bank_item.amount -= amount; + return ret; + } + + ret = bank_item.data; + this->num_items--; + for (size_t x = index; x < this->num_items; x++) { + this->items[x] = this->items[x + 1]; + } + auto& last_item = this->items[this->num_items]; + last_item.amount = 0; + last_item.present = 0; + last_item.data.clear(); + return ret; + } + + size_t find_item(uint32_t item_id) { + for (size_t x = 0; x < this->num_items; x++) { + if (this->items[x].data.id == item_id) { + return x; + } + } + throw std::out_of_range("item not present"); + } + + void sort() { + std::sort(this->items.data(), this->items.data() + this->num_items); + } + + void assign_ids(uint32_t base_id) { + for (size_t z = 0; z < this->num_items; z++) { + this->items[z].data.id = base_id + z; + } + } + + 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++) { + 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); diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 81b5bc44..97c2a7c2 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -22,184 +22,6 @@ using namespace std; -PlayerInventoryItem::PlayerInventoryItem(const ItemData& item, bool equipped) - : present(1), - unknown_a1(0), - extension_data1(0), - extension_data2(0), - flags(equipped ? 8 : 0), - data(item) {} - -uint32_t PlayerVisualConfig::compute_name_color_checksum(uint32_t name_color) { - uint8_t x = (random_object() % 0xFF) + 1; - uint8_t y = (random_object() % 0xFF) + 1; - // name_color (ARGB) = ABCDEFGHabcdefghIJKLMNOPijklmnop - // name_color_checksum = 000000000ijklmabcdeIJKLM00000000 ^ xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy - uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00); - uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y; - return xbrgx95558 ^ mask; -} - -void PlayerVisualConfig::compute_name_color_checksum() { - this->name_color_checksum = this->compute_name_color_checksum(this->name_color); -} - -void PlayerVisualConfig::enforce_lobby_join_limits_for_version(Version v) { - struct ClassMaxes { - uint16_t costume; - uint16_t skin; - uint16_t face; - uint16_t head; - uint16_t hair; - }; - static constexpr ClassMaxes v1_v2_class_maxes[14] = { - {0x0009, 0x0004, 0x0005, 0x0000, 0x0007}, - {0x0009, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0000, 0x0009, 0x0000, 0x0005, 0x0000}, - {0x0009, 0x0004, 0x0005, 0x0000, 0x0007}, - {0x0000, 0x0009, 0x0000, 0x0005, 0x0000}, - {0x0000, 0x0009, 0x0000, 0x0005, 0x0000}, - {0x0009, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0009, 0x0004, 0x0005, 0x0000, 0x0007}, - {0x0009, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - }; - static constexpr ClassMaxes v3_v4_class_maxes[19] = { - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, - {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0001}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0001}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, - {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}; - - const ClassMaxes* maxes; - if (v == Version::GC_NTE) { - // GC NTE has HUcaseal, FOmar, and RAmarl, but missing others - if (this->char_class >= 12) { - this->char_class = 0; // Invalid classes -> HUmar - } - - // GC NTE is basically v2, but uses v3 maxes - this->version = min(this->version, 2); - maxes = &v3_v4_class_maxes[this->char_class]; - - // Prevent GC NTE from crashing from extra models - this->extra_model = 0; - this->validation_flags &= 0xFD; - } else if (is_v1_or_v2(v)) { - // V1/V2 have fewer classes, so we'll substitute some here - switch (this->char_class) { - case 0: // HUmar - case 1: // HUnewearl - case 2: // HUcast - case 3: // RAmar - case 4: // RAcast - case 5: // RAcaseal - case 6: // FOmarl - case 7: // FOnewm - case 8: // FOnewearl - case 12: // V3 custom 1 - case 13: // V3 custom 2 - break; - case 9: // HUcaseal - this->char_class = 5; // HUcaseal -> RAcaseal - break; - case 10: // FOmar - this->char_class = 0; // FOmar -> HUmar - break; - case 11: // RAmarl - this->char_class = 1; // RAmarl -> HUnewearl - break; - case 14: // V2 custom 1 / V3 custom 3 - case 15: // V2 custom 2 / V3 custom 4 - case 16: // V2 custom 3 / V3 custom 5 - case 17: // V2 custom 4 / V3 custom 6 - case 18: // V2 custom 5 / V3 custom 7 - this->char_class -= 5; - break; - default: - this->char_class = 0; // Invalid classes -> HUmar - } - - this->version = min(this->version, is_v1(v) ? 0 : 2); - maxes = &v1_v2_class_maxes[this->char_class]; - - } else { - if (this->char_class >= 19) { - this->char_class = 0; // Invalid classes -> HUmar - } - this->version = min(this->version, 3); - maxes = &v3_v4_class_maxes[this->char_class]; - } - - // V1/V2 has fewer costumes and android skins, so substitute them here - this->costume = maxes->costume ? (this->costume % maxes->costume) : 0; - this->skin = maxes->skin ? (this->skin % maxes->skin) : 0; - this->face = maxes->face ? (this->face % maxes->face) : 0; - this->head = maxes->head ? (this->head % maxes->head) : 0; - this->hair = maxes->hair ? (this->hair % maxes->hair) : 0; - - if (this->name_color == 0) { - this->name_color = 0xFFFFFFFF; - } - if (is_v1_or_v2(v)) { - this->compute_name_color_checksum(); - } else { - this->name_color_checksum = 0; - } - this->class_flags = class_flags_for_class(this->char_class); - this->name.clear_after_bytes(0x0C); -} - -void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_version(Version v) { - this->visual.enforce_lobby_join_limits_for_version(v); -} - -void PlayerDispDataBB::enforce_lobby_join_limits_for_version(Version v) { - this->visual.enforce_lobby_join_limits_for_version(v); - this->name.clear_after_bytes(0x18); // 12 characters -} - -PlayerDispDataBB PlayerDispDataDCPCV3::to_bb(uint8_t to_language, uint8_t from_language) const { - PlayerDispDataBB bb; - bb.stats = this->stats; - bb.visual = this->visual; - bb.visual.name.encode(" 0"); - string decoded_name = this->visual.name.decode(from_language); - bb.name.encode(decoded_name, to_language); - bb.config = this->config; - bb.technique_levels_v1 = this->technique_levels_v1; - return bb; -} - -PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3(uint8_t to_language, uint8_t from_language) const { - PlayerDispDataDCPCV3 ret; - ret.stats = this->stats; - ret.visual = this->visual; - string decoded_name = this->name.decode(from_language); - ret.visual.name.encode(decoded_name, to_language); - ret.config = this->config; - ret.technique_levels_v1 = this->technique_levels_v1; - return ret; -} - void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { this->visual.name_color = pre.visual.name_color; this->visual.extra_model = pre.visual.extra_model; @@ -233,6 +55,106 @@ void GuildCardBB::clear() { this->char_class = 0; } +GuildCardDCNTE::operator GuildCardBB() const { + GuildCardBB ret; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardDC::operator GuildCardBB() const { + GuildCardBB ret; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardPC::operator GuildCardBB() const { + GuildCardBB ret; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardXB::operator GuildCardBB() const { + GuildCardBB ret; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardBB::operator GuildCardDCNTE() const { + GuildCardDCNTE ret; + ret.player_tag = 0x00010000; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardBB::operator GuildCardDC() const { + GuildCardDC ret; + ret.player_tag = 0x00010000; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardBB::operator GuildCardPC() const { + GuildCardPC ret; + ret.player_tag = 0x00010000; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + +GuildCardBB::operator GuildCardXB() const { + GuildCardXB ret; + ret.player_tag = 0x00010000; + ret.guild_card_number = this->guild_card_number; + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} + void PlayerLobbyDataPC::clear() { this->player_tag = 0; this->guild_card_number = 0; @@ -279,7 +201,7 @@ void PlayerLobbyDataBB::clear() { this->hide_help_prompt = 0; } -PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec) +PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengeDC& rec) : title_color(rec.title_color), unknown_u0(rec.unknown_u0), times_ep1_online(rec.times_ep1_online), @@ -303,7 +225,7 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsDC_Chall rank_title(rec.rank_title.decode(), 1), unknown_l7(0) {} -PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec) +PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengePC& rec) : title_color(rec.title_color), unknown_u0(rec.unknown_u0), times_ep1_online(rec.times_ep1_online), @@ -327,35 +249,8 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Chall rank_title(rec.rank_title.decode(), 1), unknown_l7(0) {} -PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge& rec) - : title_color(rec.stats.title_color), - unknown_u0(rec.stats.unknown_u0), - times_ep1_online(rec.stats.times_ep1_online), - times_ep2_online(rec.stats.times_ep2_online), - times_ep1_offline(rec.stats.times_ep1_offline), - grave_is_ep2(rec.stats.grave_is_ep2), - grave_stage_num(rec.stats.grave_stage_num), - grave_floor(rec.stats.grave_floor), - unknown_g0(rec.stats.unknown_g0), - grave_deaths(rec.stats.grave_deaths), - unknown_u4(rec.stats.unknown_u4), - grave_time(rec.stats.grave_time), - grave_defeated_by_enemy_rt_index(rec.stats.grave_defeated_by_enemy_rt_index), - grave_x(rec.stats.grave_x), - grave_y(rec.stats.grave_y), - grave_z(rec.stats.grave_z), - grave_team(rec.stats.grave_team.decode(), 1), - grave_message(rec.stats.grave_message.decode(), 1), - unknown_m5(rec.stats.unknown_m5), - unknown_t6(rec.stats.unknown_t6), - ep1_online_award_state(rec.stats.ep1_online_award_state), - ep2_online_award_state(rec.stats.ep2_online_award_state), - ep1_offline_award_state(rec.stats.ep1_offline_award_state), - rank_title(rec.rank_title.decode(), 1), - unknown_l7(rec.unknown_l7) {} - -PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const { - PlayerRecordsDC_Challenge ret; +PlayerRecordsChallengeBB::operator PlayerRecordsChallengeDC() const { + PlayerRecordsChallengeDC ret; ret.title_color = this->title_color; ret.unknown_u0 = this->unknown_u0; ret.rank_title.encode(this->rank_title.decode()); @@ -384,8 +279,8 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsDC_Challenge() const { return ret; } -PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const { - PlayerRecordsPC_Challenge ret; +PlayerRecordsChallengeBB::operator PlayerRecordsChallengePC() const { + PlayerRecordsChallengePC ret; ret.title_color = this->title_color; ret.unknown_u0 = this->unknown_u0; ret.rank_title.encode(this->rank_title.decode()); @@ -414,289 +309,6 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const { return ret; } -PlayerRecordsBB_Challenge::operator PlayerRecordsV3_Challenge() const { - PlayerRecordsV3_Challenge ret; - ret.stats.title_color = this->title_color; - ret.stats.unknown_u0 = this->unknown_u0; - ret.stats.times_ep1_online = this->times_ep1_online; - ret.stats.times_ep2_online = this->times_ep2_online; - ret.stats.times_ep1_offline = this->times_ep1_offline; - ret.stats.grave_is_ep2 = this->grave_is_ep2; - ret.stats.grave_stage_num = this->grave_stage_num; - ret.stats.grave_floor = this->grave_floor; - ret.stats.unknown_g0 = this->unknown_g0; - ret.stats.grave_deaths = this->grave_deaths; - ret.stats.unknown_u4 = this->unknown_u4; - ret.stats.grave_time = this->grave_time; - ret.stats.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index; - ret.stats.grave_x = this->grave_x; - ret.stats.grave_y = this->grave_y; - ret.stats.grave_z = this->grave_z; - ret.stats.grave_team.encode(this->grave_team.decode(), 1); - ret.stats.grave_message.encode(this->grave_message.decode(), 1); - ret.stats.unknown_m5 = this->unknown_m5; - ret.stats.unknown_t6 = this->unknown_t6; - ret.stats.ep1_online_award_state = this->ep1_online_award_state; - ret.stats.ep2_online_award_state = this->ep2_online_award_state; - ret.stats.ep1_offline_award_state = this->ep1_offline_award_state; - ret.rank_title.encode(this->rank_title.decode(), 1); - ret.unknown_l7 = this->unknown_l7; - return ret; -} - -void PlayerBank::add_item(const ItemData& item, const ItemData::StackLimits& limits) { - uint32_t primary_identifier = item.primary_identifier(); - - if (primary_identifier == 0x04000000) { - this->meseta += item.data2d; - if (this->meseta > 999999) { - this->meseta = 999999; - } - return; - } - - size_t combine_max = item.max_stack_size(limits); - if (combine_max > 1) { - size_t y; - for (y = 0; y < this->num_items; y++) { - if (this->items[y].data.primary_identifier() == primary_identifier) { - break; - } - } - - if (y < this->num_items) { - uint8_t new_count = this->items[y].data.data1[5] + item.data1[5]; - if (new_count > combine_max) { - throw runtime_error("stack size would exceed limit"); - } - this->items[y].data.data1[5] = new_count; - this->items[y].amount = new_count; - return; - } - } - - if (this->num_items >= 200) { - throw runtime_error("no free space in bank"); - } - auto& last_item = this->items[this->num_items]; - last_item.data = item; - last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1; - last_item.present = 1; - this->num_items++; -} - -ItemData PlayerBank::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]; - - ItemData ret; - if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) { - ret = bank_item.data; - ret.data1[5] = amount; - bank_item.data.data1[5] -= amount; - bank_item.amount -= amount; - return ret; - } - - ret = bank_item.data; - this->num_items--; - for (size_t x = index; x < this->num_items; x++) { - this->items[x] = this->items[x + 1]; - } - auto& last_item = this->items[this->num_items]; - last_item.amount = 0; - last_item.present = 0; - last_item.data.clear(); - return ret; -} - -size_t PlayerInventory::find_item(uint32_t item_id) const { - for (size_t x = 0; x < this->num_items; x++) { - if (this->items[x].data.id == item_id) { - return x; - } - } - throw out_of_range("item not present"); -} - -size_t PlayerInventory::find_item_by_primary_identifier(uint32_t primary_identifier) const { - for (size_t x = 0; x < this->num_items; x++) { - if (this->items[x].data.primary_identifier() == primary_identifier) { - return x; - } - } - throw out_of_range("item not present"); -} - -size_t PlayerInventory::find_equipped_item(EquipSlot slot) const { - ssize_t ret = -1; - for (size_t y = 0; y < this->num_items; y++) { - const auto& i = this->items[y]; - if (!(i.flags & 0x00000008)) { - continue; - } - if (!i.data.can_be_equipped_in_slot(slot)) { - continue; - } - - // Units can be equipped in multiple slots, so the currently-equipped slot - // is stored in the item data itself. - if (((slot == EquipSlot::UNIT_1) && (i.data.data1[4] != 0x00)) || - ((slot == EquipSlot::UNIT_2) && (i.data.data1[4] != 0x01)) || - ((slot == EquipSlot::UNIT_3) && (i.data.data1[4] != 0x02)) || - ((slot == EquipSlot::UNIT_4) && (i.data.data1[4] != 0x03))) { - continue; - } - - if (ret < 0) { - ret = y; - } else { - throw runtime_error("multiple items are equipped in the same slot"); - } - } - if (ret < 0) { - throw out_of_range("no item is equipped in this slot"); - } - return ret; -} - -bool PlayerInventory::has_equipped_item(EquipSlot slot) const { - try { - this->find_equipped_item(slot); - return true; - } catch (const out_of_range&) { - return false; - } -} - -void PlayerInventory::equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite) { - this->equip_item_index(this->find_item(item_id), slot, allow_overwrite); -} - -void PlayerInventory::equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite) { - auto& item = this->items[index]; - - if ((slot == EquipSlot::UNKNOWN) || !item.data.can_be_equipped_in_slot(slot)) { - slot = item.data.default_equip_slot(); - } - - if (this->has_equipped_item(slot)) { - if (allow_overwrite) { - this->unequip_item_slot(slot); - } else { - throw runtime_error("equip slot is already in use"); - } - } - - item.flags |= 0x00000008; - // Units store which slot they're equipped in within the item data itself - if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) { - item.data.data1[4] = static_cast(slot) - 9; - } -} - -void PlayerInventory::unequip_item_id(uint32_t item_id) { - this->unequip_item_index(this->find_item(item_id)); -} - -void PlayerInventory::unequip_item_slot(EquipSlot slot) { - this->unequip_item_index(this->find_equipped_item(slot)); -} - -void PlayerInventory::unequip_item_index(size_t index) { - auto& item = this->items[index]; - - item.flags &= (~0x00000008); - // Units store which slot they're equipped in within the item data itself - if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x03)) { - item.data.data1[4] = 0x00; - } - // If the item is an armor, remove all units too - if ((item.data.data1[0] == 0x01) && (item.data.data1[1] == 0x01)) { - for (size_t z = 0; z < 30; z++) { - auto& unit = this->items[z]; - if ((unit.data.data1[0] == 0x01) && (unit.data.data1[1] == 0x03)) { - unit.flags &= (~0x00000008); - unit.data.data1[4] = 0x00; - } - } - } -} - -size_t PlayerInventory::remove_all_items_of_type(uint8_t data1_0, int16_t data1_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) && - ((data1_1 < 0) || (this->items[read_offset].data.data1[1] == static_cast(data1_1)))); - if (!should_delete) { - if (read_offset != write_offset) { - this->items[write_offset].present = this->items[read_offset].present; - this->items[write_offset].unknown_a1 = this->items[read_offset].unknown_a1; - this->items[write_offset].flags = this->items[read_offset].flags; - this->items[write_offset].data = this->items[read_offset].data; - } - write_offset++; - } - } - size_t ret = this->num_items - write_offset; - this->num_items = write_offset; - return ret; -} - -void PlayerInventory::decode_from_client(shared_ptr c) { - for (size_t z = 0; z < this->items.size(); z++) { - this->items[z].data.decode_for_version(c->version()); - } -} - -void PlayerInventory::encode_for_client(shared_ptr c) { - Version v = c->version(); - if (v == Version::DC_NTE) { - // DC NTE has the item count as a 32-bit value here, whereas every other - // version uses a single byte. To stop DC NTE from crashing by trying to - // construct far more than 30 TItem objects, we clear the fields DC NTE - // doesn't know about. Note that the 11/2000 prototype does not have this - // issue - its inventory format matches the rest of the versions. - this->hp_from_materials = 0; - this->tp_from_materials = 0; - this->language = 0; - } else if ((v != Version::PC_NTE) && (v != Version::PC_V2)) { - if (this->language > 4) { - this->language = 0; - } - } else { - if (this->language > 7) { - this->language = 0; - } - } - - // For pre-V2 clients, use the V2 parameter table, since the V1 table doesn't - // have correct encodings for backward-compatible V2 items. - auto item_parameter_table = c->require_server_state()->item_parameter_table_for_encode(v); - for (size_t z = 0; z < this->items.size(); z++) { - this->items[z].data.encode_for_version(v, item_parameter_table); - } -} - -size_t PlayerBank::find_item(uint32_t item_id) { - for (size_t x = 0; x < this->num_items; x++) { - if (this->items[x].data.id == item_id) { - return x; - } - } - throw out_of_range("item not present"); -} - -void PlayerBank::sort() { - std::sort(this->items.data(), this->items.data() + this->num_items); -} - -void PlayerBank::assign_ids(uint32_t base_id) { - for (size_t z = 0; z < this->num_items; z++) { - this->items[z].data.id = base_id + z; - } -} - QuestFlagsV1& QuestFlagsV1::operator=(const QuestFlags& other) { this->data[0] = other.data[0]; this->data[1] = other.data[1]; @@ -1087,11 +699,6 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver } } -SymbolChat::SymbolChat() - : spec(0), - corner_objects(0x00FF), - face_parts() {} - void RecentSwitchFlags::add(uint16_t flag_num) { if ((flag_num != ((this->flag_nums >> 48) & 0xFFFF)) && (flag_num != ((this->flag_nums >> 32) & 0xFFFF)) && diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 489fa63b..4530b2f3 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -15,106 +15,25 @@ #include "ItemData.hh" #include "LevelTable.hh" #include "PSOEncryption.hh" +#include "PlayerInventory.hh" +#include "StaticGameData.hh" #include "Text.hh" #include "Version.hh" class Client; class ItemParameterTable; -// PSO V2 stored some extra data in the character structs in a format that I'm -// sure Sega thought was very clever for backward compatibility, but for us is -// just plain annoying. Specifically, they used the third and fourth bytes of -// the InventoryItem struct to store some things not present in V1. The game -// stores arrays of bytes striped across these structures. In newserv, we call -// those fields extension_data. They contain: -// items[0].extension_data1 through items[19].extension_data1: -// Extended technique levels. The values in the technique_levels_v1 array -// only go up to 14 (tech level 15); if the player has a technique above -// level 15, the corresponding extension_data1 field holds the remaining -// levels (so a level 20 tech would have 14 in technique_levels_v1 and 5 -// in the corresponding item's extension_data1 field). -// items[0].extension_data2 through items[3].extension_data2: -// The flags field from the PSOGCCharacterFile::Character struct; see -// SaveFileFormats.hh for details. -// items[4].extension_data2 through items[7].extension_data2: -// The timestamp when the character was last saved, in seconds since -// January 1, 2000. Stored little-endian, so items[4] contains the LSB. -// items[8].extension_data2 through items[12].extension_data2: -// Number of power materials, mind materials, evade materials, def -// materials, and luck materials (respectively) used by the player. -// items[13].extension_data2 through items[15].extension_data2: -// Unknown. These are not an array, but do appear to be related. - -struct PlayerInventoryItem { - /* 00 */ uint8_t present = 0; - /* 01 */ uint8_t unknown_a1 = 0; - // See note above about these fields - /* 02 */ uint8_t extension_data1 = 0; - /* 03 */ uint8_t extension_data2 = 0; - /* 04 */ le_uint32_t flags = 0; // 8 = equipped - /* 08 */ ItemData data; - /* 1C */ - - PlayerInventoryItem() = default; - explicit PlayerInventoryItem(const ItemData& item, bool equipped = false); -} __attribute__((packed)); - -struct PlayerBankItem { - /* 00 */ ItemData data; - /* 14 */ le_uint16_t amount = 0; - /* 16 */ le_uint16_t present = 0; - /* 18 */ - - inline bool operator<(const PlayerBankItem& other) const { - return this->data < other.data; - } -} __attribute__((packed)); - -struct PlayerInventory { - /* 0000 */ uint8_t num_items = 0; - /* 0001 */ uint8_t hp_from_materials = 0; - /* 0002 */ uint8_t tp_from_materials = 0; - /* 0003 */ uint8_t language = 0; - /* 0004 */ parray items; - /* 034C */ - - size_t find_item(uint32_t item_id) const; - size_t find_item_by_primary_identifier(uint32_t primary_identifier) const; - - size_t find_equipped_item(EquipSlot slot) const; - bool has_equipped_item(EquipSlot slot) const; - void equip_item_id(uint32_t item_id, EquipSlot slot, bool allow_overwrite); - void equip_item_index(size_t index, EquipSlot slot, bool allow_overwrite); - void unequip_item_id(uint32_t item_id); - void unequip_item_slot(EquipSlot slot); - void unequip_item_index(size_t index); - - size_t remove_all_items_of_type(uint8_t data0, int16_t data1 = -1); - - void decode_from_client(std::shared_ptr c); - void encode_for_client(std::shared_ptr c); -} __attribute__((packed)); - -struct PlayerBank { - /* 0000 */ le_uint32_t num_items = 0; - /* 0004 */ le_uint32_t meseta = 0; - /* 0008 */ parray items; - /* 12C8 */ - - void add_item(const ItemData& item, const ItemData::StackLimits& limits); - ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits); - size_t find_item(uint32_t item_id); - - void sort(); - void assign_ids(uint32_t base_id); -} __attribute__((packed)); - struct PlayerDispDataBB; -struct PlayerVisualConfig { +template +struct PlayerVisualConfigT { + using U16T = typename std::conditional::type; + using U32T = typename std::conditional::type; + using F32T = typename std::conditional::type; + /* 00 */ pstring name; /* 10 */ parray unknown_a2; - /* 18 */ le_uint32_t name_color = 0xFFFFFFFF; // ARGB + /* 18 */ U32T name_color = 0xFFFFFFFF; // ARGB /* 1C */ uint8_t extra_model = 0; /* 1D */ parray unused; // See compute_name_color_checksum for details on how this is computed. If the @@ -122,7 +41,7 @@ struct PlayerVisualConfig { // default color instead. This field is ignored on GC; on BB (and presumably // Xbox), if this has a nonzero value, the "Change Name" option appears in the // character selection menu. - /* 2C */ le_uint32_t name_color_checksum = 0; + /* 2C */ U32T name_color_checksum = 0; /* 30 */ uint8_t section_id = 0; /* 31 */ uint8_t char_class = 0; // validation_flags specifies that some parts of this structure are not valid @@ -138,35 +57,206 @@ struct PlayerVisualConfig { // F = force, R = ranger, H = hunter // A = android, N = newman, M = human // f = female, m = male - /* 34 */ le_uint32_t class_flags = 0; - /* 38 */ le_uint16_t costume = 0; - /* 3A */ le_uint16_t skin = 0; - /* 3C */ le_uint16_t face = 0; - /* 3E */ le_uint16_t head = 0; - /* 40 */ le_uint16_t hair = 0; - /* 42 */ le_uint16_t hair_r = 0; - /* 44 */ le_uint16_t hair_g = 0; - /* 46 */ le_uint16_t hair_b = 0; - /* 48 */ le_float proportion_x = 0.0; - /* 4C */ le_float proportion_y = 0.0; + /* 34 */ U32T class_flags = 0; + /* 38 */ U16T costume = 0; + /* 3A */ U16T skin = 0; + /* 3C */ U16T face = 0; + /* 3E */ U16T head = 0; + /* 40 */ U16T hair = 0; + /* 42 */ U16T hair_r = 0; + /* 44 */ U16T hair_g = 0; + /* 46 */ U16T hair_b = 0; + /* 48 */ F32T proportion_x = 0.0; + /* 4C */ F32T proportion_y = 0.0; /* 50 */ - static uint32_t compute_name_color_checksum(uint32_t name_color); - void compute_name_color_checksum(); + static uint32_t compute_name_color_checksum(uint32_t name_color) { + uint8_t x = (random_object() % 0xFF) + 1; + uint8_t y = (random_object() % 0xFF) + 1; + // name_color (ARGB) = ABCDEFGHabcdefghIJKLMNOPijklmnop + // name_color_checksum = 000000000ijklmabcdeIJKLM00000000 ^ xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy + uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00); + uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y; + return xbrgx95558 ^ mask; + } - void enforce_lobby_join_limits_for_version(Version v); -} __attribute__((packed)); + void compute_name_color_checksum() { + this->name_color_checksum = this->compute_name_color_checksum(this->name_color); + } -struct PlayerDispDataDCPCV3 { - /* 00 */ PlayerStats stats; - /* 24 */ PlayerVisualConfig visual; + void enforce_lobby_join_limits_for_version(Version v) { + struct ClassMaxes { + uint16_t costume; + uint16_t skin; + uint16_t face; + uint16_t head; + uint16_t hair; + }; + static constexpr ClassMaxes v1_v2_class_maxes[14] = { + {0x0009, 0x0004, 0x0005, 0x0000, 0x0007}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0000, 0x0009, 0x0000, 0x0005, 0x0000}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x0007}, + {0x0000, 0x0009, 0x0000, 0x0005, 0x0000}, + {0x0000, 0x0009, 0x0000, 0x0005, 0x0000}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x0007}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + }; + static constexpr ClassMaxes v3_v4_class_maxes[19] = { + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0001}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0001}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}; + + const ClassMaxes* maxes; + if (v == Version::GC_NTE) { + // GC NTE has HUcaseal, FOmar, and RAmarl, but missing others + if (this->char_class >= 12) { + this->char_class = 0; // Invalid classes -> HUmar + } + + // GC NTE is basically v2, but uses v3 maxes + this->version = std::min(this->version, 2); + maxes = &v3_v4_class_maxes[this->char_class]; + + // Prevent GC NTE from crashing from extra models + this->extra_model = 0; + this->validation_flags &= 0xFD; + } else if (is_v1_or_v2(v)) { + // V1/V2 have fewer classes, so we'll substitute some here + switch (this->char_class) { + case 0: // HUmar + case 1: // HUnewearl + case 2: // HUcast + case 3: // RAmar + case 4: // RAcast + case 5: // RAcaseal + case 6: // FOmarl + case 7: // FOnewm + case 8: // FOnewearl + case 12: // V3 custom 1 + case 13: // V3 custom 2 + break; + case 9: // HUcaseal + this->char_class = 5; // HUcaseal -> RAcaseal + break; + case 10: // FOmar + this->char_class = 0; // FOmar -> HUmar + break; + case 11: // RAmarl + this->char_class = 1; // RAmarl -> HUnewearl + break; + case 14: // V2 custom 1 / V3 custom 3 + case 15: // V2 custom 2 / V3 custom 4 + case 16: // V2 custom 3 / V3 custom 5 + case 17: // V2 custom 4 / V3 custom 6 + case 18: // V2 custom 5 / V3 custom 7 + this->char_class -= 5; + break; + default: + this->char_class = 0; // Invalid classes -> HUmar + } + + this->version = std::min(this->version, is_v1(v) ? 0 : 2); + maxes = &v1_v2_class_maxes[this->char_class]; + + } else { + if (this->char_class >= 19) { + this->char_class = 0; // Invalid classes -> HUmar + } + this->version = std::min(this->version, 3); + maxes = &v3_v4_class_maxes[this->char_class]; + } + + // V1/V2 has fewer costumes and android skins, so substitute them here + this->costume = maxes->costume ? (this->costume % maxes->costume) : 0; + this->skin = maxes->skin ? (this->skin % maxes->skin) : 0; + this->face = maxes->face ? (this->face % maxes->face) : 0; + this->head = maxes->head ? (this->head % maxes->head) : 0; + this->hair = maxes->hair ? (this->hair % maxes->hair) : 0; + + if (this->name_color == 0) { + this->name_color = 0xFFFFFFFF; + } + if (is_v1_or_v2(v)) { + this->compute_name_color_checksum(); + } else { + this->name_color_checksum = 0; + } + this->class_flags = class_flags_for_class(this->char_class); + this->name.clear_after_bytes(0x0C); + } + + operator PlayerVisualConfigT() const { + PlayerVisualConfigT ret; + ret.name = this->name; + ret.unknown_a2 = this->unknown_a2; + ret.name_color = this->name_color.load(); + ret.extra_model = this->extra_model; + ret.unused = this->unused; + ret.name_color_checksum = this->name_color_checksum.load(); + ret.section_id = this->section_id; + ret.char_class = this->char_class; + ret.validation_flags = this->validation_flags; + ret.version = this->version; + ret.class_flags = this->class_flags.load(); + ret.costume = this->costume.load(); + ret.skin = this->skin.load(); + ret.face = this->face.load(); + ret.head = this->head.load(); + ret.hair = this->hair.load(); + ret.hair_r = this->hair_r.load(); + ret.hair_g = this->hair_g.load(); + ret.hair_b = this->hair_b.load(); + ret.proportion_x = this->proportion_x.load(); + ret.proportion_y = this->proportion_y.load(); + return ret; + } +} __packed__; +using PlayerVisualConfig = PlayerVisualConfigT; +using PlayerVisualConfigBE = PlayerVisualConfigT; +check_struct_size(PlayerVisualConfig, 0x50); +check_struct_size(PlayerVisualConfigBE, 0x50); + +template +struct PlayerDispDataDCPCV3T { + /* 00 */ PlayerStatsT stats; + /* 24 */ PlayerVisualConfigT visual; /* 74 */ parray config; /* BC */ parray technique_levels_v1; /* D0 */ - void enforce_lobby_join_limits_for_version(Version v); + void enforce_lobby_join_limits_for_version(Version v) { + this->visual.enforce_lobby_join_limits_for_version(v); + } + PlayerDispDataBB to_bb(uint8_t to_language, uint8_t from_language) const; -} __attribute__((packed)); +} __packed__; +using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T; +using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T; +check_struct_size(PlayerDispDataDCPCV3, 0xD0); +check_struct_size(PlayerDispDataDCPCV3BE, 0xD0); struct PlayerDispDataBBPreview { /* 00 */ le_uint32_t experience = 0; @@ -177,7 +267,7 @@ struct PlayerDispDataBBPreview { /* 58 */ pstring name; /* 78 */ uint32_t play_time_seconds = 0; /* 7C */ -} __attribute__((packed)); +} __packed_ws__(PlayerDispDataBBPreview, 0x7C); // BB player appearance and stats data struct PlayerDispDataBB { @@ -188,14 +278,44 @@ struct PlayerDispDataBB { /* 017C */ parray technique_levels_v1; /* 0190 */ - void enforce_lobby_join_limits_for_version(Version v); - PlayerDispDataDCPCV3 to_dcpcv3(uint8_t to_language, uint8_t from_language) const; + void enforce_lobby_join_limits_for_version(Version v) { + this->visual.enforce_lobby_join_limits_for_version(v); + this->name.clear_after_bytes(0x18); // 12 characters + } + + template + PlayerDispDataDCPCV3T to_dcpcv3(uint8_t to_language, uint8_t from_language) const { + PlayerDispDataDCPCV3T ret; + ret.stats = this->stats; + ret.visual = this->visual; + std::string decoded_name = this->name.decode(from_language); + ret.visual.name.encode(decoded_name, to_language); + ret.config = this->config; + ret.technique_levels_v1 = this->technique_levels_v1; + return ret; + } + void apply_preview(const PlayerDispDataBBPreview&); void apply_dressing_room(const PlayerDispDataBBPreview&); -} __attribute__((packed)); +} __packed_ws__(PlayerDispDataBB, 0x190); + +template +PlayerDispDataBB PlayerDispDataDCPCV3T::to_bb(uint8_t to_language, uint8_t from_language) const { + PlayerDispDataBB bb; + bb.stats = this->stats; + bb.visual = this->visual; + bb.visual.name.encode(" 0"); + std::string decoded_name = this->visual.name.decode(from_language); + bb.name.encode(decoded_name, to_language); + bb.config = this->config; + bb.technique_levels_v1 = this->technique_levels_v1; + return bb; +} + +struct GuildCardBB; struct GuildCardDCNTE { - /* 00 */ le_uint32_t player_tag = 0; + /* 00 */ le_uint32_t player_tag = 0x00010000; /* 04 */ le_uint32_t guild_card_number = 0; /* 08 */ pstring name; /* 20 */ pstring description; @@ -205,10 +325,12 @@ struct GuildCardDCNTE { /* 79 */ uint8_t section_id = 0; /* 7A */ uint8_t char_class = 0; /* 7B */ -} __attribute__((packed)); + + operator GuildCardBB() const; +} __packed_ws__(GuildCardDCNTE, 0x7B); struct GuildCardDC { - /* 00 */ le_uint32_t player_tag = 0; + /* 00 */ le_uint32_t player_tag = 0x00010000; /* 04 */ le_uint32_t guild_card_number = 0; /* 08 */ pstring name; /* 20 */ pstring description; @@ -218,10 +340,12 @@ struct GuildCardDC { /* 7B */ uint8_t section_id = 0; /* 7C */ uint8_t char_class = 0; /* 7D */ -} __attribute__((packed)); + + operator GuildCardBB() const; +} __packed_ws__(GuildCardDC, 0x7D); struct GuildCardPC { - /* 00 */ le_uint32_t player_tag = 0; + /* 00 */ le_uint32_t player_tag = 0x00010000; /* 04 */ le_uint32_t guild_card_number = 0; // TODO: Is the length of the name field correct here? /* 08 */ pstring name; @@ -231,11 +355,16 @@ struct GuildCardPC { /* EE */ uint8_t section_id = 0; /* EF */ uint8_t char_class = 0; /* F0 */ -} __attribute__((packed)); -struct GuildCardGC { - /* 00 */ le_uint32_t player_tag = 0; - /* 04 */ le_uint32_t guild_card_number = 0; + operator GuildCardBB() const; +} __packed_ws__(GuildCardPC, 0xF0); + +template +struct GuildCardGCT { + using U32T = typename std::conditional::type; + + /* 00 */ U32T player_tag = 0x00010000; + /* 04 */ U32T guild_card_number = 0; /* 08 */ pstring name; /* 20 */ pstring description; /* 8C */ uint8_t present = 0; @@ -243,10 +372,16 @@ struct GuildCardGC { /* 8E */ uint8_t section_id = 0; /* 8F */ uint8_t char_class = 0; /* 90 */ -} __attribute__((packed)); + + operator GuildCardBB() const; +} __packed__; +using GuildCardGC = GuildCardGCT; +using GuildCardGCBE = GuildCardGCT; +check_struct_size(GuildCardGC, 0x90); +check_struct_size(GuildCardGCBE, 0x90); struct GuildCardXB { - /* 0000 */ le_uint32_t player_tag = 0; + /* 0000 */ le_uint32_t player_tag = 0x00010000; /* 0004 */ le_uint32_t guild_card_number = 0; /* 0008 */ le_uint32_t xb_user_id_high = 0; /* 000C */ le_uint32_t xb_user_id_low = 0; @@ -257,7 +392,9 @@ struct GuildCardXB { /* 022A */ uint8_t section_id = 0; /* 022B */ uint8_t char_class = 0; /* 022C */ -} __attribute__((packed)); + + operator GuildCardBB() const; +} __packed_ws__(GuildCardXB, 0x22C); struct GuildCardBB { /* 0000 */ le_uint32_t guild_card_number = 0; @@ -271,7 +408,38 @@ struct GuildCardBB { /* 0108 */ void clear(); -} __attribute__((packed)); + + operator GuildCardDCNTE() const; + operator GuildCardDC() const; + operator GuildCardPC() const; + template + operator GuildCardGCT() const { + GuildCardGCT ret; + ret.player_tag = 0x00010000; + ret.guild_card_number = this->guild_card_number.load(); + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; + } + operator GuildCardXB() const; +} __packed_ws__(GuildCardBB, 0x108); + +template +GuildCardGCT::operator GuildCardBB() const { + GuildCardBB ret; + ret.guild_card_number = this->guild_card_number.load(); + ret.name.encode(this->name.decode(this->language), this->language); + ret.description.encode(this->description.decode(this->language), this->language); + ret.present = this->present; + ret.language = this->language; + ret.section_id = this->section_id; + ret.char_class = this->char_class; + return ret; +} struct PlayerLobbyDataPC { le_uint32_t player_tag = 0; @@ -286,7 +454,7 @@ struct PlayerLobbyDataPC { pstring name; void clear(); -} __attribute__((packed)); +} __packed_ws__(PlayerLobbyDataPC, 0x30); struct PlayerLobbyDataDCGC { le_uint32_t player_tag = 0; @@ -296,7 +464,7 @@ struct PlayerLobbyDataDCGC { pstring name; void clear(); -} __attribute__((packed)); +} __packed_ws__(PlayerLobbyDataDCGC, 0x20); struct XBNetworkLocation { /* 00 */ le_uint32_t internal_ipv4_address = 0x0A0A0A0A; @@ -307,21 +475,21 @@ struct XBNetworkLocation { /* 14 */ le_uint32_t unknown_a2; /* 18 */ le_uint64_t account_id = 0xFFFFFFFFFFFFFFFF; /* 20 */ parray unknown_a3; - /* 24 */ + /* 30 */ void clear(); -} __attribute__((packed)); +} __packed_ws__(XBNetworkLocation, 0x30); struct PlayerLobbyDataXB { /* 00 */ le_uint32_t player_tag = 0; /* 04 */ le_uint32_t guild_card_number = 0; /* 08 */ XBNetworkLocation netloc; - /* 2C */ le_uint32_t client_id = 0; - /* 30 */ pstring name; - /* 40 */ + /* 38 */ le_uint32_t client_id = 0; + /* 3C */ pstring name; + /* 4C */ void clear(); -} __attribute__((packed)); +} __packed_ws__(PlayerLobbyDataXB, 0x4C); struct PlayerLobbyDataBB { /* 00 */ le_uint32_t player_tag = 0; @@ -334,25 +502,36 @@ struct PlayerLobbyDataBB { // If this field is zero, the "Press F1 for help" prompt appears in the corner // of the screen in the lobby and on Pioneer 2. /* 40 */ le_uint32_t hide_help_prompt = 1; - /* 44 */ + /* 44 */ void clear(); -} __attribute__((packed)); +} __packed_ws__(PlayerLobbyDataBB, 0x44); template -struct ChallengeAwardState { +struct ChallengeAwardStateT { using U32T = typename std::conditional::type; U32T rank_award_flags = 0; - ChallengeTime maximum_rank; -} __attribute__((packed)); + ChallengeTimeT maximum_rank; + + operator ChallengeAwardStateT() const { + ChallengeAwardStateT ret; + ret.rank_award_flags = this->rank_award_flags.load(); + ret.maximum_rank = this->maximum_rank; + return ret; + } +} __packed__; +using ChallengeAwardState = ChallengeAwardStateT; +using ChallengeAwardStateBE = ChallengeAwardStateT; +check_struct_size(ChallengeAwardState, 8); +check_struct_size(ChallengeAwardStateBE, 8); template -struct PlayerRecordsDCPC_Challenge { +struct PlayerRecordsChallengeDCPCT { /* DC:PC */ /* 00:00 */ le_uint16_t title_color = 0x7FFF; /* 02:02 */ parray unknown_u0; /* 04:04 */ pstring rank_title; - /* 10:1C */ parray, 9> times_ep1_online; // TODO: This might be offline times + /* 10:1C */ parray, 9> times_ep1_online; // TODO: This might be offline times /* 34:40 */ uint8_t grave_stage_num = 0; /* 35:41 */ uint8_t grave_floor = 0; /* 36:42 */ le_uint16_t grave_deaths = 0; @@ -370,51 +549,49 @@ struct PlayerRecordsDCPC_Challenge { /* 48:54 */ le_float grave_z = 0.0f; /* 4C:58 */ pstring grave_team; /* 60:80 */ pstring grave_message; - /* 78:B0 */ parray, 9> times_ep1_offline; // TODO: This might be online times + /* 78:B0 */ parray, 9> times_ep1_offline; // TODO: This might be online times /* 9C:D4 */ parray unknown_l4; /* A0:D8 */ -} __attribute__((packed)); - -struct PlayerRecordsDC_Challenge : PlayerRecordsDCPC_Challenge { -} __attribute__((packed)); - -struct PlayerRecordsPC_Challenge : PlayerRecordsDCPC_Challenge { -} __attribute__((packed)); +} __packed__; +using PlayerRecordsChallengeDC = PlayerRecordsChallengeDCPCT; +using PlayerRecordsChallengePC = PlayerRecordsChallengeDCPCT; +check_struct_size(PlayerRecordsChallengeDC, 0xA0); +check_struct_size(PlayerRecordsChallengePC, 0xD8); template -struct PlayerRecordsV3_Challenge { +struct PlayerRecordsChallengeV3T { using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; - using FloatT = typename std::conditional::type; + using F32T = typename std::conditional::type; // Offsets are (1) relative to start of C5 entry, and (2) relative to start // of save file structure struct Stats { /* 00:1C */ U16T title_color = 0x7FFF; // XRGB1555 /* 02:1E */ parray unknown_u0; - /* 04:20 */ parray, 9> times_ep1_online; - /* 28:44 */ parray, 5> times_ep2_online; - /* 3C:58 */ parray, 9> times_ep1_offline; + /* 04:20 */ parray, 9> times_ep1_online; + /* 28:44 */ parray, 5> times_ep2_online; + /* 3C:58 */ parray, 9> times_ep1_offline; /* 60:7C */ uint8_t grave_is_ep2 = 0; /* 61:7D */ uint8_t grave_stage_num = 0; /* 62:7E */ uint8_t grave_floor = 0; /* 63:7F */ uint8_t unknown_g0 = 0; /* 64:80 */ U16T grave_deaths = 0; /* 66:82 */ parray unknown_u4; - /* 68:84 */ U32T grave_time = 0; // Encoded as in PlayerRecordsDCPC_Challenge + /* 68:84 */ U32T grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC /* 6C:88 */ U32T grave_defeated_by_enemy_rt_index = 0; - /* 70:8C */ FloatT grave_x = 0.0f; - /* 74:90 */ FloatT grave_y = 0.0f; - /* 78:94 */ FloatT grave_z = 0.0f; + /* 70:8C */ F32T grave_x = 0.0f; + /* 74:90 */ F32T grave_y = 0.0f; + /* 78:94 */ F32T grave_z = 0.0f; /* 7C:98 */ pstring grave_team; /* 90:AC */ pstring grave_message; /* B0:CC */ parray unknown_m5; /* B4:D0 */ parray unknown_t6; - /* C0:DC */ ChallengeAwardState ep1_online_award_state; - /* C8:E4 */ ChallengeAwardState ep2_online_award_state; - /* D0:EC */ ChallengeAwardState ep1_offline_award_state; + /* C0:DC */ ChallengeAwardStateT ep1_online_award_state; + /* C8:E4 */ ChallengeAwardStateT ep2_online_award_state; + /* D0:EC */ ChallengeAwardStateT ep1_offline_award_state; /* D8:F4 */ - } __attribute__((packed)); + } __packed__; /* 0000:001C */ Stats stats; // On Episode 3, there are special cases that apply to this field - if the // text ends with certain strings, the player will have particle effects @@ -426,21 +603,25 @@ struct PlayerRecordsV3_Challenge { /* 00D8:00F4 */ pstring rank_title; /* 00E4:0100 */ parray unknown_l7; /* 0100:011C */ -} __attribute__((packed)); +} __packed__; +using PlayerRecordsChallengeV3 = PlayerRecordsChallengeV3T; +using PlayerRecordsChallengeV3BE = PlayerRecordsChallengeV3T; +check_struct_size(PlayerRecordsChallengeV3, 0x100); +check_struct_size(PlayerRecordsChallengeV3BE, 0x100); -struct PlayerRecordsBB_Challenge { +struct PlayerRecordsChallengeBB { /* 0000 */ le_uint16_t title_color = 0x7FFF; // XRGB1555 /* 0002 */ parray unknown_u0; - /* 0004 */ parray, 9> times_ep1_online; - /* 0028 */ parray, 5> times_ep2_online; - /* 003C */ parray, 9> times_ep1_offline; + /* 0004 */ parray, 9> times_ep1_online; + /* 0028 */ parray, 5> times_ep2_online; + /* 003C */ parray, 9> times_ep1_offline; /* 0060 */ uint8_t grave_is_ep2 = 0; /* 0061 */ uint8_t grave_stage_num = 0; /* 0062 */ uint8_t grave_floor = 0; /* 0063 */ uint8_t unknown_g0 = 0; /* 0064 */ le_uint16_t grave_deaths = 0; /* 0066 */ parray unknown_u4; - /* 0068 */ le_uint32_t grave_time = 0; // Encoded as in PlayerRecordsDCPC_Challenge + /* 0068 */ le_uint32_t grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC /* 006C */ le_uint32_t grave_defeated_by_enemy_rt_index = 0; /* 0070 */ le_float grave_x = 0.0f; /* 0074 */ le_float grave_y = 0.0f; @@ -449,36 +630,118 @@ struct PlayerRecordsBB_Challenge { /* 00A4 */ pstring grave_message; /* 00E4 */ parray unknown_m5; /* 00E8 */ parray unknown_t6; - /* 00F4 */ ChallengeAwardState ep1_online_award_state; - /* 00FC */ ChallengeAwardState ep2_online_award_state; - /* 0104 */ ChallengeAwardState ep1_offline_award_state; + /* 00F4 */ ChallengeAwardStateT ep1_online_award_state; + /* 00FC */ ChallengeAwardStateT ep2_online_award_state; + /* 0104 */ ChallengeAwardStateT ep1_offline_award_state; /* 010C */ pstring rank_title; /* 0124 */ parray unknown_l7; /* 0140 */ - PlayerRecordsBB_Challenge() = default; - PlayerRecordsBB_Challenge(const PlayerRecordsBB_Challenge& other) = default; - PlayerRecordsBB_Challenge& operator=(const PlayerRecordsBB_Challenge& other) = default; + PlayerRecordsChallengeBB() = default; + PlayerRecordsChallengeBB(const PlayerRecordsChallengeBB& other) = default; + PlayerRecordsChallengeBB& operator=(const PlayerRecordsChallengeBB& other) = default; - PlayerRecordsBB_Challenge(const PlayerRecordsDC_Challenge& rec); - PlayerRecordsBB_Challenge(const PlayerRecordsPC_Challenge& rec); - PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge& rec); + PlayerRecordsChallengeBB(const PlayerRecordsChallengeDC& rec); + PlayerRecordsChallengeBB(const PlayerRecordsChallengePC& rec); - operator PlayerRecordsDC_Challenge() const; - operator PlayerRecordsPC_Challenge() const; - operator PlayerRecordsV3_Challenge() const; -} __attribute__((packed)); + template + PlayerRecordsChallengeBB(const PlayerRecordsChallengeV3T& rec) + : title_color(rec.stats.title_color.load()), + unknown_u0(rec.stats.unknown_u0), + times_ep1_online(rec.stats.times_ep1_online), + times_ep2_online(rec.stats.times_ep2_online), + times_ep1_offline(rec.stats.times_ep1_offline), + grave_is_ep2(rec.stats.grave_is_ep2), + grave_stage_num(rec.stats.grave_stage_num), + grave_floor(rec.stats.grave_floor), + unknown_g0(rec.stats.unknown_g0), + grave_deaths(rec.stats.grave_deaths.load()), + unknown_u4(rec.stats.unknown_u4), + grave_time(rec.stats.grave_time.load()), + grave_defeated_by_enemy_rt_index(rec.stats.grave_defeated_by_enemy_rt_index.load()), + grave_x(rec.stats.grave_x.load()), + grave_y(rec.stats.grave_y.load()), + grave_z(rec.stats.grave_z.load()), + grave_team(rec.stats.grave_team.decode(), 1), + grave_message(rec.stats.grave_message.decode(), 1), + unknown_m5(rec.stats.unknown_m5), + ep1_online_award_state(rec.stats.ep1_online_award_state), + ep2_online_award_state(rec.stats.ep2_online_award_state), + ep1_offline_award_state(rec.stats.ep1_offline_award_state), + rank_title(rec.rank_title.decode(), 1), + unknown_l7(rec.unknown_l7) { + for (size_t z = 0; z < std::min(this->unknown_t6.size(), rec.stats.unknown_t6.size()); z++) { + this->unknown_t6[z] = rec.stats.unknown_t6[z].load(); + } + } + + operator PlayerRecordsChallengeDC() const; + operator PlayerRecordsChallengePC() const; + template + operator PlayerRecordsChallengeV3T() const { + PlayerRecordsChallengeV3T ret; + ret.stats.title_color = this->title_color.load(); + ret.stats.unknown_u0 = this->unknown_u0; + ret.stats.times_ep1_online = this->times_ep1_online; + ret.stats.times_ep2_online = this->times_ep2_online; + ret.stats.times_ep1_offline = this->times_ep1_offline; + ret.stats.grave_is_ep2 = this->grave_is_ep2; + ret.stats.grave_stage_num = this->grave_stage_num; + ret.stats.grave_floor = this->grave_floor; + ret.stats.unknown_g0 = this->unknown_g0; + ret.stats.grave_deaths = this->grave_deaths.load(); + ret.stats.unknown_u4 = this->unknown_u4; + ret.stats.grave_time = this->grave_time.load(); + ret.stats.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index.load(); + ret.stats.grave_x = this->grave_x.load(); + ret.stats.grave_y = this->grave_y.load(); + ret.stats.grave_z = this->grave_z.load(); + ret.stats.grave_team.encode(this->grave_team.decode(), 1); + ret.stats.grave_message.encode(this->grave_message.decode(), 1); + ret.stats.unknown_m5 = this->unknown_m5; + for (size_t z = 0; z < std::min(ret.stats.unknown_t6.size(), this->unknown_t6.size()); z++) { + ret.stats.unknown_t6[z] = this->unknown_t6[z].load(); + } + ret.stats.ep1_online_award_state = this->ep1_online_award_state; + ret.stats.ep2_online_award_state = this->ep2_online_award_state; + ret.stats.ep1_offline_award_state = this->ep1_offline_award_state; + ret.rank_title.encode(this->rank_title.decode(), 1); + ret.unknown_l7 = this->unknown_l7; + return ret; + } +} __packed_ws__(PlayerRecordsChallengeBB, 0x140); template -struct PlayerRecords_Battle { +struct PlayerRecordsBattleT { using U16T = typename std::conditional::type; + using U32T = typename std::conditional::type; + // On Episode 3, place_counts[0] is win count and [1] is loss count /* 00 */ parray place_counts; /* 08 */ U16T disconnect_count = 0; - /* 0A */ parray unknown_a1; - /* 10 */ parray unknown_a2; + /* 0A */ parray unknown_a1; + /* 10 */ parray unknown_a2; /* 18 */ -} __attribute__((packed)); + + operator PlayerRecordsBattleT() const { + PlayerRecordsBattleT ret; + for (size_t z = 0; z < this->place_counts.size(); z++) { + ret.place_counts[z] = this->place_counts[z].load(); + } + ret.disconnect_count = this->disconnect_count.load(); + for (size_t z = 0; z < this->unknown_a1.size(); z++) { + ret.unknown_a1[z] = this->unknown_a1[z].load(); + } + for (size_t z = 0; z < this->unknown_a2.size(); z++) { + ret.unknown_a2[z] = this->unknown_a2[z].load(); + } + return ret; + } +} __packed__; +using PlayerRecordsBattle = PlayerRecordsBattleT; +using PlayerRecordsBattleBE = PlayerRecordsBattleT; +check_struct_size(PlayerRecordsBattle, 0x18); +check_struct_size(PlayerRecordsBattleBE, 0x18); template DestT convert_player_disp_data(const SrcT&, uint8_t, uint8_t) { @@ -494,7 +757,7 @@ inline PlayerDispDataDCPCV3 convert_player_disp_data(const template <> inline PlayerDispDataDCPCV3 convert_player_disp_data( const PlayerDispDataBB& src, uint8_t to_language, uint8_t from_language) { - return src.to_dcpcv3(to_language, from_language); + return src.to_dcpcv3(to_language, from_language); } template <> @@ -534,7 +797,7 @@ struct QuestFlagsForDifficulty { this->data.clear(0x00); } } -} __attribute__((packed)); +} __packed_ws__(QuestFlagsForDifficulty, 0x80); struct QuestFlags { parray data; @@ -556,14 +819,14 @@ struct QuestFlags { this->update_all(z, set); } } -} __attribute__((packed)); +} __packed_ws__(QuestFlags, 0x200); struct QuestFlagsV1 { parray data; QuestFlagsV1& operator=(const QuestFlags& other); operator QuestFlags() const; -} __attribute__((packed)); +} __packed_ws__(QuestFlagsV1, 0x180); struct SwitchFlags { parray, 0x12> data; @@ -577,7 +840,7 @@ struct SwitchFlags { inline void clear(uint8_t floor, uint16_t flag_num) { this->data[floor][flag_num >> 3] &= ~(0x80 >> (flag_num & 7)); } -} __attribute__((packed)); +} __packed_ws__(SwitchFlags, 0x240); struct BattleRules { enum class TechDiskMode : uint8_t { @@ -696,7 +959,7 @@ struct BattleRules { bool operator==(const BattleRules& other) const = default; bool operator!=(const BattleRules& other) const = default; -} __attribute__((packed)); +} __packed_ws__(BattleRules, 0x30); struct ChallengeTemplateDefinition { uint32_t level; @@ -710,29 +973,50 @@ struct ChallengeTemplateDefinition { const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index); -struct SymbolChat { +struct SymbolChatFacePart { + uint8_t type = 0xFF; // FF = no part in this slot + uint8_t x = 0; + uint8_t y = 0; + // Bits: ------VH (V = reverse vertical, H = reverse horizontal) + uint8_t flags = 0; +} __packed_ws__(SymbolChatFacePart, 4); + +template +struct SymbolChatT { + using U16T = typename std::conditional::type; + using U32T = typename std::conditional::type; + // Bits: ----------------------DMSSSCCCFF // S = sound, C = face color, F = face shape, D = capture, M = mute sound - /* 00 */ le_uint32_t spec = 0; + /* 00 */ U32T spec = 0; // Corner objects are specified in reading order ([0] is the top-left one). // Bits (each entry): ---VHCCCZZZZZZZZ // V = reverse vertical, H = reverse horizontal, C = color, Z = object // If Z is all 1 bits (0xFF), no corner object is rendered. - /* 04 */ parray corner_objects; - - struct FacePart { - uint8_t type = 0xFF; // FF = no part in this slot - uint8_t x = 0; - uint8_t y = 0; - // Bits: ------VH (V = reverse vertical, H = reverse horizontal) - uint8_t flags = 0; - } __attribute__((packed)); - /* 0C */ parray face_parts; + /* 04 */ parray corner_objects; + /* 0C */ parray face_parts; /* 3C */ - SymbolChat(); -} __attribute__((packed)); + SymbolChatT() + : spec(0), + corner_objects(0x00FF), + face_parts() {} + + operator SymbolChatT() const { + SymbolChatT ret; + ret.spec = this->spec.load(); + for (size_t z = 0; z < this->corner_objects.size(); z++) { + ret.corner_objects[z] = this->corner_objects[z].load(); + } + ret.face_parts = this->face_parts; + return ret; + } +} __packed__; +using SymbolChat = SymbolChatT; +using SymbolChatBE = SymbolChatT; +check_struct_size(SymbolChat, 0x3C); +check_struct_size(SymbolChatBE, 0x3C); struct RecentSwitchFlags { uint64_t flag_nums = 0xFFFFFFFFFFFFFFFF; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 9f03dbf1..ef750f87 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -701,7 +701,7 @@ static HandlerResult S_B2(shared_ptr ses, uint16_t, ses->log.info("Wrote code from server to file %s", output_filename.c_str()); #ifdef HAVE_RESOURCE_FILE - using FooterT = S_ExecuteCode_Footer_B2; + using FooterT = S_ExecuteCode_FooterT_B2; using U16T = typename std::conditional::type; using U32T = typename std::conditional::type; // TODO: Support SH-4 disassembly too diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index a4395930..dc497023 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -837,7 +837,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) } string port_name = login_port_name_for_version(this->version()); - S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}}; + S_Reconnect_19 reconnect_cmd = {0, s->name_to_port_config.at(port_name)->port, 0}; // If the client is on a virtual connection, we can use any address // here and they should be able to connect back to the game server diff --git a/src/Quest.cc b/src/Quest.cc index 163b5934..b0fcf362 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -43,7 +43,7 @@ shared_ptr QuestCategoryIndex::at(uint32_t c // GCI decoding logic template -struct PSOMemCardDLQFileEncryptedHeader { +struct PSOMemCardDLQFileEncryptedHeaderT { using U32T = typename std::conditional::type; U32T round2_seed; @@ -54,12 +54,11 @@ struct PSOMemCardDLQFileEncryptedHeader { le_uint32_t decompressed_size; le_uint32_t round3_seed; // Data follows here. -} __attribute__((packed)); - -struct PSOVMSDLQFileEncryptedHeader : PSOMemCardDLQFileEncryptedHeader { -} __attribute__((packed)); -struct PSOGCIDLQFileEncryptedHeader : PSOMemCardDLQFileEncryptedHeader { -} __attribute__((packed)); +} __packed__; +using PSOVMSDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT; +using PSOGCIDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT; +check_struct_size(PSOVMSDLQFileEncryptedHeader, 0x10); +check_struct_size(PSOGCIDLQFileEncryptedHeader, 0x10); template string decrypt_download_quest_data_section( @@ -73,7 +72,7 @@ string decrypt_download_quest_data_section( // not at the beginning. Presumably they did this because the system, // character, and Guild Card files are a constant size, but download quest // files can vary in size. - using HeaderT = PSOMemCardDLQFileEncryptedHeader; + using HeaderT = PSOMemCardDLQFileEncryptedHeaderT; auto* header = reinterpret_cast(decrypted.data()); PSOV2Encryption round2_crypt(header->round2_seed); round2_crypt.encrypt_t( @@ -193,7 +192,7 @@ string find_seed_and_decrypt_download_quest_data_section( struct PSODownloadQuestHeader { le_uint32_t size; le_uint32_t encryption_seed; -} __attribute__((packed)); +} __packed_ws__(PSODownloadQuestHeader, 8); VersionedQuest::VersionedQuest( uint32_t quest_number, diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 8f16b978..7a6dd41a 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -117,7 +117,7 @@ static string format_and_indent_data(const void* data, size_t size, uint64_t sta struct UnknownF8F2Entry { parray unknown_a1; -} __attribute__((packed)); +} __packed_ws__(UnknownF8F2Entry, 0x10); struct QuestScriptOpcodeDefinition { struct Argument { diff --git a/src/QuestScript.hh b/src/QuestScript.hh index da8ddf6a..a8363a91 100644 --- a/src/QuestScript.hh +++ b/src/QuestScript.hh @@ -16,7 +16,7 @@ struct PSOQuestHeaderDCNTE { /* 000C */ le_uint32_t unused; /* 0010 */ pstring name; /* 0020 */ -} __attribute__((packed)); +} __packed_ws__(PSOQuestHeaderDCNTE, 0x20); struct PSOQuestHeaderDC { // Same format for DC v1 and v2 /* 0000 */ le_uint32_t code_offset; @@ -30,7 +30,7 @@ struct PSOQuestHeaderDC { // Same format for DC v1 and v2 /* 0034 */ pstring short_description; /* 00B4 */ pstring long_description; /* 01D4 */ -} __attribute__((packed)); +} __packed_ws__(PSOQuestHeaderDC, 0x1D4); struct PSOQuestHeaderPC { /* 0000 */ le_uint32_t code_offset; @@ -44,7 +44,7 @@ struct PSOQuestHeaderPC { /* 0054 */ pstring short_description; /* 0154 */ pstring long_description; /* 0394 */ -} __attribute__((packed)); +} __packed_ws__(PSOQuestHeaderPC, 0x394); // TODO: Is the XB quest header format the same as on GC? If not, make a // separate struct; if so, rename this struct to V3. @@ -61,7 +61,7 @@ struct PSOQuestHeaderGC { /* 0034 */ pstring short_description; /* 00B4 */ pstring long_description; /* 01D4 */ -} __attribute__((packed)); +} __packed_ws__(PSOQuestHeaderGC, 0x1D4); struct PSOQuestHeaderBB { /* 0000 */ le_uint32_t code_offset; @@ -78,7 +78,7 @@ struct PSOQuestHeaderBB { /* 0058 */ pstring short_description; /* 0158 */ pstring long_description; /* 0398 */ -} __attribute__((packed)); +} __packed_ws__(PSOQuestHeaderBB, 0x398); Episode episode_for_quest_episode_number(uint8_t episode_number); diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index 8ea0720e..6668b0bd 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -97,7 +97,7 @@ void RareItemSet::ParsedRELData::parse_t(StringReader r, bool is_v1) { using U32T = typename std::conditional::type; uint32_t root_offset = r.pget(r.size() - 0x10); - const auto& root = r.pget>(root_offset); + const auto& root = r.pget>(root_offset); StringReader monsters_r = r.sub(root.monster_rares_offset); for (size_t z = 0; z < (is_v1 ? 0x33 : 0x65); z++) { @@ -125,7 +125,7 @@ std::string RareItemSet::ParsedRELData::serialize_t(bool is_v1) const { static const PackedDrop empty_drop; - Offsets root; + OffsetsT root; root.box_count = this->box_rares.size(); StringWriter w; diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index bc746cc4..9c318ee2 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -64,17 +64,21 @@ protected: PackedDrop() = default; explicit PackedDrop(const ExpandedDrop&); ExpandedDrop expand() const; - } __attribute__((packed)); + } __packed_ws__(PackedDrop, 4); template - struct Offsets { + struct OffsetsT { using U32T = typename std::conditional::type; /* 00 */ U32T monster_rares_offset; // -> parray (or 0x33 on v1) /* 04 */ U32T box_count; // Usually 30 (0x1E) /* 08 */ U32T box_areas_offset; // -> parray /* 0C */ U32T box_rares_offset; // -> parray /* 10 */ - } __attribute__((packed)); + } __packed__; + using Offsets = OffsetsT; + using OffsetsBE = OffsetsT; + check_struct_size(Offsets, 0x10); + check_struct_size(OffsetsBE, 0x10); struct BoxRare { uint8_t area; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 7870415e..67f97bfb 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1715,7 +1715,7 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { l->battle_record->add_player( lobby_data, existing_p->inventory, - existing_p->disp.to_dcpcv3(c->language(), c->language()), + existing_p->disp.to_dcpcv3(c->language(), c->language()), c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0); } } @@ -3208,7 +3208,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri default: throw logic_error("player data command not implemented for version"); } - player->inventory.decode_from_client(c); + player->inventory.decode_from_client(c->version()); c->channel.language = player->inventory.language; c->license->save(); @@ -3258,7 +3258,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (is_v1_or_v2(c->version())) { bb_player->disp.stats = player->disp.stats; } else { - bb_player->disp.stats.advance_to_level(bb_player->disp.visual.char_class, player->disp.stats.level, s->level_table); + s->level_table->advance_to_level(bb_player->disp.stats, player->disp.stats.level, bb_player->disp.visual.char_class); bb_player->disp.stats.char_stats.atp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::POWER) * 2; bb_player->disp.stats.char_stats.mst += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::MIND) * 2; bb_player->disp.stats.char_stats.evp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::EVADE) * 2; @@ -3276,7 +3276,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri bb_player->choice_search_config = player->choice_search_config; try { Client::save_character_file(filename, c->system_file(), bb_player); - send_text_message(c, "$C7Character data saved"); + send_text_message(c, "$C7Character data saved\n(basic only)"); } catch (const exception& e) { send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what()); } @@ -3291,6 +3291,65 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } } +static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { + if (!c->pending_character_export) { + c->log.warning("No pending export is present"); + return; + } + unique_ptr pending_export = std::move(c->pending_character_export); + c->pending_character_export.reset(); + + string filename; + if (pending_export->is_bb_conversion) { + filename = Client::character_filename( + pending_export->license->bb_username, + pending_export->character_index); + } else { + filename = Client::backup_character_filename( + pending_export->license->serial_number, + pending_export->character_index); + } + + auto s = c->require_server_state(); + if (s->player_files_manager->get_character(filename)) { + send_text_message(c, "$C6The target player\nis currently loaded.\nSign off in Blue\nBurst and try again."); + return; + } + + shared_ptr bb_char; + switch (c->version()) { + case Version::GC_V3: { + auto gc_char = check_size_t(data); + bb_char = PSOBBCharacterFile::create_from_gc(gc_char); + break; + } + 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: + case Version::GC_EP3_NTE: + case Version::GC_EP3: + case Version::XB_V3: + case Version::BB_V4: + default: + throw logic_error("extended player data command not implemented for version"); + } + + bb_char->inventory.decode_from_client(c->version()); + bb_char->disp.visual.version = 4; + bb_char->disp.visual.name_color_checksum = 0x00000000; + + try { + Client::save_character_file(filename, c->system_file(), bb_char); + send_text_message(c, "$C7Character data saved\n(full save file)"); + } catch (const exception& e) { + send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what()); + } +} + static void on_6x_C9_CB(shared_ptr c, uint16_t command, uint32_t flag, string& data) { check_size_v(data.size(), 4, 0xFFFF); if ((data.size() > 0x400) && (command != 0x6C) && (command != 0x6D)) { @@ -3655,7 +3714,7 @@ static void on_ED_BB(shared_ptr c, uint16_t command, uint32_t, string& d } case 0x06ED: { const auto& cmd = check_size_t(data); - p->tech_menu_config = cmd.tech_menu; + p->tech_menu_shortcut_entries = cmd.tech_menu; break; } case 0x07ED: { @@ -4379,7 +4438,7 @@ static void on_0C_C1_E7_EC(shared_ptr c, uint16_t command, uint32_t, str shared_ptr game; if ((c->version() == Version::DC_NTE) || (c->version() == Version::DC_V1_11_2000_PROTOTYPE)) { - const auto& cmd = check_size_t>(data); + const auto& cmd = check_size_t(data); game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, true); } else { @@ -5277,7 +5336,7 @@ static on_command_t handlers[0x100][NUM_VERSIONS - 2] = { /* 2E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 2F */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, // DC_NTE DC_PROTO DCV1 DCV2 PC-NTE PC GCNTE GC EP3TE EP3 XB BB -/* 30 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, +/* 30 */ {on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30, on_30}, /* 31 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 32 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* 33 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 3d89151b..54763265 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1231,7 +1231,7 @@ static void on_symbol_chat(shared_ptr c, uint8_t command, uint8_t flag, template static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, void* data, size_t size) { - const auto& cmd = check_size_t>(data, size); + const auto& cmd = check_size_t>(data, size); if (c->can_chat && (cmd.client_id == c->lobby_client_id)) { if (command_is_private(command)) { return; @@ -1282,12 +1282,12 @@ static void on_word_select_t(shared_ptr c, uint8_t command, uint8_t, voi } if (is_big_endian(lc->version())) { - G_WordSelect_6x74 out_cmd = { + G_WordSelectBE_6x74 out_cmd = { subcommand, cmd.size, cmd.client_id.load(), s->word_select_table->translate(cmd.message, from_version, lc_version)}; send_command_t(lc, 0x60, 0x00, out_cmd); } else { - G_WordSelect_6x74 out_cmd = { + G_WordSelect_6x74 out_cmd = { subcommand, cmd.size, cmd.client_id.load(), s->word_select_table->translate(cmd.message, from_version, lc_version)}; send_command_t(lc, 0x60, 0x00, out_cmd); @@ -2982,7 +2982,7 @@ static void on_activate_timed_switch(shared_ptr c, uint8_t command, uint } static void on_battle_scores(shared_ptr c, uint8_t command, uint8_t, void* data, size_t size) { - const auto& cmd = check_size_t>(data, size); + const auto& cmd = check_size_t(data, size); if (command_is_private(command)) { return; @@ -2992,7 +2992,7 @@ static void on_battle_scores(shared_ptr c, uint8_t command, uint8_t, voi return; } - G_BattleScores_6x7F sw_cmd; + G_BattleScoresBE_6x7F sw_cmd; sw_cmd.header.subcommand = 0x7F; sw_cmd.header.size = cmd.header.size; sw_cmd.header.unused = 0; @@ -3026,8 +3026,8 @@ static void on_dragon_actions(shared_ptr c, uint8_t command, uint8_t, vo return; } - G_DragonBossActions_GC_6x12 sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, - cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load()}}; + G_DragonBossActions_GC_6x12 sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, + cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load()}; bool sender_is_be = is_big_endian(c->version()); for (auto lc : l->clients) { if (lc && (lc != c)) { @@ -3051,14 +3051,14 @@ static void on_gol_dragon_actions(shared_ptr c, uint8_t command, uint8_t return; } - G_GolDragonBossActions_GC_6xA8 sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, + G_GolDragonBossActions_GC_6xA8 sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.unknown_a2, cmd.unknown_a3, cmd.unknown_a4, cmd.x.load(), cmd.z.load(), cmd.unknown_a5, - 0}}; + 0}; bool sender_is_be = is_big_endian(c->version()); for (auto lc : l->clients) { if (lc && (lc != c)) { @@ -3095,7 +3095,7 @@ static void on_update_enemy_state(shared_ptr c, uint8_t command, uint8_t l->log.info("E-%hX updated to damage=%hu game_flags=%08" PRIX32, cmd.enemy_index.load(), enemy.total_damage, enemy.game_flags); } - G_UpdateEnemyState_GC_6x0A sw_cmd = {{{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()}}; + G_UpdateEnemyState_GC_6x0A sw_cmd = {{cmd.header.subcommand, cmd.header.size, cmd.header.enemy_id}, cmd.enemy_index, cmd.total_damage, cmd.flags.load()}; bool sender_is_be = is_big_endian(c->version()); for (auto lc : l->clients) { if (lc && (lc != c)) { @@ -3752,7 +3752,7 @@ static void on_battle_level_up_bb(shared_ptr c, uint8_t, uint8_t, void* auto lp = lc->character(); uint32_t target_level = lp->disp.stats.level + cmd.num_levels; uint32_t before_exp = lp->disp.stats.experience; - lp->disp.stats.advance_to_level(lp->disp.visual.char_class, target_level, s->level_table); + s->level_table->advance_to_level(lp->disp.stats, target_level, lp->disp.visual.char_class); send_give_experience(lc, lp->disp.stats.experience - before_exp); send_level_up(lc); } @@ -4626,10 +4626,10 @@ void on_subcommand_multi(shared_ptr c, uint8_t command, uint8_t flag, st if (header->size != 0) { cmd_size = header->size << 2; } else { - if (offset + sizeof(G_ExtendedHeader) > data.size()) { + if (offset + sizeof(G_ExtendedHeaderT) > data.size()) { throw runtime_error("insufficient data remaining for next extended subcommand header"); } - const auto* ext_header = reinterpret_cast*>(data.data() + offset); + const auto* ext_header = reinterpret_cast*>(data.data() + offset); cmd_size = ext_header->size; if (cmd_size < 8) { throw runtime_error("extended subcommand header has size < 8"); diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index b59ef3a1..2efd3835 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -388,7 +388,7 @@ shared_ptr PSOBBCharacterFile::create_from_config( ret->disp.config[z] = config[z]; } - ret->disp.stats.reset_to_base(ret->disp.visual.char_class, level_table); + level_table->reset_to_base(ret->disp.stats, ret->disp.visual.char_class); ret->disp.technique_levels_v1.clear(0xFF); if (ret->disp.visual.class_flags & 0x80) { ret->disp.technique_levels_v1[0] = 0x00; // Forces start with Foie Lv.1 @@ -404,7 +404,7 @@ shared_ptr PSOBBCharacterFile::create_from_config( ret->symbol_chats[z] = PSOBBCharacterFile::DEFAULT_SYMBOL_CHATS[z].to_entry(language); } for (size_t z = 0; z < PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG.size(); z++) { - ret->tech_menu_config[z] = PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG[z]; + ret->tech_menu_shortcut_entries[z] = PSOBBCharacterFile::DEFAULT_TECH_MENU_CONFIG[z]; } return ret; } @@ -418,16 +418,99 @@ shared_ptr PSOBBCharacterFile::create_from_preview( guild_card_number, language, preview.visual, preview.name.decode(language), level_table); } -PSOBBCharacterFile::SymbolChatEntry PSOBBCharacterFile::DefaultSymbolChatEntry::to_entry(uint8_t language) const { - SymbolChatEntry ret; +shared_ptr PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) { + auto ret = make_shared(); + ret->inventory = gc.inventory; + uint8_t language = ret->inventory.language; + ret->disp = gc.disp.to_bb(language, language); + ret->creation_timestamp = gc.creation_timestamp.load(); + ret->play_time_seconds = gc.play_time_seconds.load(); + ret->option_flags = gc.option_flags.load(); + ret->save_count = gc.save_count.load(); + ret->quest_flags = gc.quest_flags; + ret->death_count = gc.death_count.load(); + ret->bank = gc.bank; + ret->guild_card = gc.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), gc.symbol_chats.size()); z++) { + auto& ret_sc = ret->symbol_chats[z]; + const auto& gc_sc = gc.symbol_chats[z]; + ret_sc.present = gc_sc.present.load(); + ret_sc.name.encode(gc_sc.name.decode(language), language); + 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->auto_reply.encode(gc.auto_reply.decode(language), language); + ret->info_board.encode(gc.info_board.decode(language), language); + ret->battle_records = gc.battle_records; + ret->unknown_a4 = gc.unknown_a4; + ret->challenge_records = gc.challenge_records; + for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), gc.tech_menu_shortcut_entries.size()); z++) { + ret->tech_menu_shortcut_entries[z] = gc.tech_menu_shortcut_entries[z].load(); + } + ret->choice_search_config = gc.choice_search_config; + ret->unknown_a6 = gc.unknown_a6; + for (size_t z = 0; z < std::min(ret->quest_counters.size(), gc.quest_counters.size()); z++) { + ret->quest_counters[z] = gc.quest_counters[z].load(); + } + ret->offline_battle_records = gc.offline_battle_records; + ret->unknown_a7 = gc.unknown_a7; + return ret; +} + +PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const { + uint8_t language = this->inventory.language; + + PSOGCCharacterFile::Character ret; + ret.inventory = this->inventory; + ret.disp = this->disp.to_dcpcv3(language, language); + ret.creation_timestamp = this->creation_timestamp.load(); + ret.play_time_seconds = this->play_time_seconds.load(); + ret.option_flags = this->option_flags.load(); + ret.save_count = this->save_count.load(); + ret.quest_flags = this->quest_flags; + ret.death_count = this->death_count.load(); + ret.bank = this->bank; + ret.guild_card = this->guild_card; + for (size_t z = 0; z < std::min(ret.symbol_chats.size(), this->symbol_chats.size()); z++) { + auto& ret_sc = ret.symbol_chats[z]; + const auto& gc_sc = this->symbol_chats[z]; + ret_sc.present = gc_sc.present.load(); + ret_sc.name.encode(gc_sc.name.decode(language), language); + 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.auto_reply.encode(this->auto_reply.decode(language), language); + ret.info_board.encode(this->info_board.decode(language), language); + ret.battle_records = this->battle_records; + ret.unknown_a4 = this->unknown_a4; + ret.challenge_records = this->challenge_records; + for (size_t z = 0; z < std::min(ret.tech_menu_shortcut_entries.size(), this->tech_menu_shortcut_entries.size()); z++) { + ret.tech_menu_shortcut_entries[z] = this->tech_menu_shortcut_entries[z].load(); + } + ret.choice_search_config = this->choice_search_config; + ret.unknown_a6 = this->unknown_a6; + for (size_t z = 0; z < std::min(ret.quest_counters.size(), this->quest_counters.size()); z++) { + ret.quest_counters[z] = this->quest_counters[z].load(); + } + ret.offline_battle_records = this->offline_battle_records; + ret.unknown_a7 = this->unknown_a7; + 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.data.spec = this->spec; + ret.spec.spec = this->spec; for (size_t z = 0; z < 4; z++) { - ret.data.corner_objects[z] = this->corner_objects[z]; + ret.spec.corner_objects[z] = this->corner_objects[z]; } for (size_t z = 0; z < 12; z++) { - ret.data.face_parts[z] = this->face_parts[z]; + ret.spec.face_parts[z] = this->face_parts[z]; } return ret; } @@ -617,12 +700,12 @@ 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}, {SymbolChat::FacePart{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}, {SymbolChat::FacePart{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}, {SymbolChat::FacePart{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}, {SymbolChat::FacePart{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}, {SymbolChat::FacePart{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}, {SymbolChat::FacePart{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}}}, + 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 = { diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 81b20933..d3cad45b 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -29,9 +29,9 @@ struct ShuffleTables { }; struct PSOVMSFileHeader { - /* 0000 */ pstring short_desc; - /* 0010 */ pstring long_desc; - /* 0030 */ pstring creator_id; + /* 0000 */ pstring short_desc; + /* 0010 */ pstring long_desc; + /* 0030 */ pstring creator_id; /* 0040 */ le_uint16_t num_icons; /* 0042 */ le_uint16_t animation_speed; /* 0044 */ le_uint16_t eyecatch_type; @@ -43,7 +43,7 @@ struct PSOVMSFileHeader { /* 0080 */ // parray icon; bool checksum_correct() const; -} __attribute__((packed)); +} __packed_ws__(PSOVMSFileHeader, 0x80); struct PSOGCIFileHeader { // Every PSOGC save file begins with a PSOGCIFileHeader. The first 0x40 bytes @@ -57,7 +57,7 @@ struct PSOGCIFileHeader { // There is a structure for this part of the header, but we don't use it. /* 0006 */ uint8_t unused; /* 0007 */ uint8_t image_flags; - /* 0008 */ pstring internal_file_name; + /* 0008 */ pstring internal_file_name; /* 0028 */ be_uint32_t modification_time; /* 002C */ be_uint32_t image_data_offset; /* 0030 */ be_uint16_t icon_formats; @@ -70,9 +70,9 @@ struct PSOGCIFileHeader { /* 003C */ be_uint32_t comment_offset; // GCI header ends here (and memcard file data begins here) // game_name is e.g. "PSO EPISODE I & II" or "PSO EPISODE III" - /* 0040 */ pstring game_name; + /* 0040 */ pstring game_name; /* 005C */ be_uint32_t embedded_seed; // Used in some of Ralf's quest packs - /* 0060 */ pstring file_name; + /* 0060 */ pstring file_name; /* 0080 */ parray banner; /* 1880 */ parray icon; // data_size specifies the number of bytes remaining in the file. In all cases @@ -90,7 +90,7 @@ struct PSOGCIFileHeader { bool is_ep12() const; bool is_ep3() const; bool is_nte() const; -} __attribute__((packed)); +} __packed_ws__(PSOGCIFileHeader, 0x2088); struct PSOGCSystemFile { /* 0000 */ be_uint32_t checksum; @@ -110,7 +110,7 @@ struct PSOGCSystemFile { // Guild Card files. /* 0118 */ be_uint32_t creation_timestamp; /* 011C */ -} __attribute__((packed)); +} __packed_ws__(PSOGCSystemFile, 0x11C); struct PSOGCEp3SystemFile { /* 0000 */ PSOGCSystemFile base; @@ -118,196 +118,99 @@ struct PSOGCEp3SystemFile { /* 011D */ parray unknown_a2; /* 0128 */ be_uint32_t unknown_a3; /* 012C */ -} __attribute__((packed)); +} __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 */ -} __attribute__((packed)); +template +struct SaveFileSymbolChatEntryT { + using U32T = std::conditional_t; -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 */ + /* PC:GC:BB */ + /* 00:00:00 */ U32T present; + /* 04:04:04 */ pstring name; + /* 34:1C:2C */ SymbolChatT spec; + /* 70:58:68 */ +} __packed__; +using SaveFileSymbolChatEntryPC = SaveFileSymbolChatEntryT; +using SaveFileSymbolChatEntryGC = SaveFileSymbolChatEntryT; +using SaveFileSymbolChatEntryBB = SaveFileSymbolChatEntryT; +check_struct_size(SaveFileSymbolChatEntryPC, 0x70); +check_struct_size(SaveFileSymbolChatEntryGC, 0x58); +check_struct_size(SaveFileSymbolChatEntryBB, 0x68); - PSOBBTeamMembership() = default; -} __attribute__((packed)); +template +struct WordSelectMessageT { + using U16T = std::conditional_t; + using U32T = std::conditional_t; -struct PSOBBBaseSystemFile { - /* 0000 */ PSOBBMinimalSystemFile base; - /* 0114 */ parray key_config; - /* 0280 */ parray joystick_config; - /* 02B8 */ + U16T num_tokens = 0; + U16T target_type = 0; + parray tokens; + U32T numeric_parameter = 0; + U32T unknown_a4 = 0; - static const std::array DEFAULT_KEY_CONFIG; - static const std::array DEFAULT_JOYSTICK_CONFIG; + operator WordSelectMessageT() const { + WordSelectMessageT ret; + ret.num_tokens = this->num_tokens.load(); + ret.target_type = this->target_type.load(); + for (size_t z = 0; z < this->tokens.size(); z++) { + ret.tokens[z] = this->tokens[z].load(); + } + ret.numeric_parameter = this->numeric_parameter.load(); + ret.unknown_a4 = this->unknown_a4.load(); + return ret; + } +} __packed__; +using WordSelectMessage = WordSelectMessageT; +using WordSelectMessageBE = WordSelectMessageT; +check_struct_size(WordSelectMessage, 0x1C); +check_struct_size(WordSelectMessageBE, 0x1C); - PSOBBBaseSystemFile(); -} __attribute__((packed)); +template +struct SaveFileChatShortcutEntryT { + using U32T = std::conditional_t; -struct PSOBBFullSystemFile { - /* 0000 */ PSOBBBaseSystemFile base; - /* 02B8 */ PSOBBTeamMembership team_membership; - /* 0AF0 */ + union Definition { + pstring text; + WordSelectMessageT word_select; + SymbolChatT symbol_chat; - PSOBBFullSystemFile() = default; -} __attribute__((packed)); + Definition() : text() {} + Definition(const Definition& other) : text(other.text) {} + Definition& operator=(const Definition& other) { + this->text = other.text; + return *this; + } + } __packed__; -struct PSOBBCharacterFile { - struct SymbolChatEntry { - /* 00 */ le_uint32_t present = 0; - /* 04 */ pstring name; - /* 2C */ SymbolChat data; - /* 68 */ - } __attribute__((packed)); + /* GC:BB */ + /* 00:00 */ U32T type; // 1 = text, 2 = word select, 3 = symbol chat + /* 04:04 */ Definition definition; + /* 54:A4 */ - struct DefaultSymbolChatEntry { - std::array language_to_name; - uint32_t spec; - std::array corner_objects; - std::array face_parts; - - SymbolChatEntry 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 */ parray unknown_a2; - /* 04F4 */ QuestFlags quest_flags; - /* 06F4 */ le_uint32_t death_count = 0; - /* 06F8 */ PlayerBank 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 */ PlayerRecords_Battle battle_records; - /* 2CB4 */ parray unknown_a4; - /* 2CB8 */ PlayerRecordsBB_Challenge challenge_records; - /* 2DF8 */ parray tech_menu_config; - /* 2E20 */ ChoiceSearchConfig choice_search_config; - /* 2E38 */ parray unknown_a6; - /* 2E48 */ parray quest_counters; - /* 2E88 */ 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); - - 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(); -} __attribute__((packed)); - -struct PSOBBGuildCardFile { - struct Entry { - /* 0000 */ GuildCardBB data; - /* 0108 */ pstring comment; - /* 01B8 */ parray unknown_a1; - /* 01BC */ - - void clear(); - } __attribute__((packed)); - - /* 0000 */ PSOBBMinimalSystemFile system_file; - /* 0114 */ parray blocked; - /* 1DF4 */ parray unknown_a2; - /* 1F74 */ parray entries; - /* D590 */ - - PSOBBGuildCardFile() = default; - - uint32_t checksum() const; -} __attribute__((packed)); - -struct PSOGCSaveFileSymbolChatEntry { - /* 00 */ be_uint32_t present; - /* 04 */ pstring name; - /* 1C */ be_uint32_t spec; - struct CornerObject { - uint8_t type; - uint8_t flags_color; - } __attribute__((packed)); - /* 20 */ parray corner_objects; - struct FacePart { - uint8_t type; - uint8_t x; - uint8_t y; - uint8_t flags; - } __attribute__((packed)); - /* 28 */ parray face_parts; - /* 58 */ -} __attribute__((packed)); - -struct PSOPCSaveFileSymbolChatEntry { - /* 00 */ le_uint32_t present; - /* 04 */ pstring name; - /* 34 */ SymbolChat data; - /* 70 */ -} __attribute__((packed)); - -struct PSOGCSaveFileChatShortcutEntry { - /* 00 */ be_uint32_t present_type; - /* 04 */ parray definition; - /* 54 */ -} __attribute__((packed)); + template + SaveFileChatShortcutEntryT convert(uint8_t language) const { + SaveFileChatShortcutEntryT ret; + ret.type = this->type.load(); + switch (ret.type) { + case 1: + ret.definition.text.encode(this->definition.text.decode(language), language); + break; + case 2: + // TODO: We should translate the message across PSO versions if + // possible, but this is a lossy process :| + ret.definition.word_select = this->definition.word_select; + break; + case 3: + ret.definition.symbol_chat = this->definition.symbol_chat; + break; + } + return ret; + } +} __packed__; +using SaveFileShortcutEntryGC = SaveFileChatShortcutEntryT; +using SaveFileShortcutEntryBB = SaveFileChatShortcutEntryT; +check_struct_size(SaveFileShortcutEntryGC, 0x54); +check_struct_size(SaveFileShortcutEntryBB, 0xA4); struct PSOGCCharacterFile { /* 00000 */ be_uint32_t checksum; @@ -315,18 +218,18 @@ struct PSOGCCharacterFile { // 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 // to the start of the second internal structure (second column). - /* 0000:---- */ PlayerInventory inventory; - /* 034C:---- */ PlayerDispDataDCPCV3 disp; + /* 0000:---- */ PlayerInventoryBE inventory; + /* 034C:---- */ PlayerDispDataDCPCV3BE disp; // Known bits in the flags field: // 00000001: Character was not saved after disconnecting (and the message // about items being deleted is shown in the select menu) // 00000002: Used for something, but it's not known what it does - /* 041C:0000 */ be_uint32_t flags; - /* 0420:0004 */ be_uint32_t creation_timestamp; + /* 041C:0000 */ be_uint32_t flags = 0; + /* 0420:0004 */ be_uint32_t creation_timestamp = 0; // The signature field holds the value 0xA205B064, which is 2718281828 in // decimal - approximately e * 10^9. It's unknown why Sega chose this value. - /* 0424:0008 */ be_uint32_t signature; - /* 0428:000C */ be_uint32_t play_time_seconds; + /* 0424:0008 */ be_uint32_t signature = 0xA205B064; + /* 0428:000C */ be_uint32_t play_time_seconds = 0; // This field is a collection of several flags and small values. The known // fields are: // ------zA BCDEFG-- HHHIIIJJ KLMNOPQR @@ -344,32 +247,32 @@ struct PSOGCCharacterFile { // P = Cursor position (0 = saved; 1 = non-saved) // Q = Button config (0 = normal; 1 = L/R reversed) // R = Map direction (0 = non-fixed; 1 = fixed) - /* 042C:0010 */ be_uint32_t option_flags; - /* 0430:0014 */ be_uint32_t save_count; - /* 0434:0018 */ parray unknown_a4; - /* 0450:0034 */ parray unknown_a5; + /* 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; /* 0460:0044 */ QuestFlags quest_flags; - /* 0660:0244 */ be_uint32_t death_count; - /* 0664:0248 */ PlayerBank bank; - /* 192C:1510 */ GuildCardGC guild_card; - /* 19BC:15A0 */ parray symbol_chats; - /* 1DDC:19C0 */ parray chat_shortcuts; - /* 246C:2050 */ pstring auto_reply; - /* 2518:20FC */ pstring info_board; - /* 25C4:21A8 */ PlayerRecords_Battle battle_records; - /* 25DC:21C0 */ parray unknown_a2; - /* 25E0:21C4 */ PlayerRecordsV3_Challenge challenge_records; + /* 0660:0244 */ be_uint32_t death_count = 0; + /* 0664:0248 */ PlayerBankBE bank; + /* 192C:1510 */ GuildCardGCBE guild_card; + /* 19BC:15A0 */ parray symbol_chats; + /* 1DDC:19C0 */ parray shortcuts; + /* 246C:2050 */ pstring auto_reply; + /* 2518:20FC */ pstring info_board; + /* 25C4:21A8 */ PlayerRecordsBattleBE battle_records; + /* 25DC:21C0 */ parray unknown_a4; + /* 25E0:21C4 */ PlayerRecordsChallengeV3BE challenge_records; /* 26E0:22C4 */ parray tech_menu_shortcut_entries; - /* 2708:22EC */ ChoiceSearchConfig choice_search_config; + /* 2708:22EC */ ChoiceSearchConfigBE choice_search_config; /* 2720:2304 */ parray unknown_a6; /* 2730:2314 */ parray quest_counters; - /* 2770:2354 */ PlayerRecords_Battle offline_battle_records; - /* 2788:236C */ parray unknown_f5; - /* 278C:2370 */ be_uint32_t unknown_f6; - /* 2790:2374 */ be_uint32_t unknown_f7; - /* 2794:2378 */ be_uint32_t unknown_f8; + /* 2770:2354 */ PlayerRecordsBattleBE offline_battle_records; + /* 2788:236C */ parray unknown_a7; + /* 278C:2370 */ be_uint32_t unknown_f6 = 0; + /* 2790:2374 */ be_uint32_t unknown_f7 = 0; + /* 2794:2378 */ be_uint32_t unknown_f8 = 0; /* 2798:237C */ - } __attribute__((packed)); + } __packed_ws__(Character, 0x2798); /* 00004 */ parray characters; /* 1152C */ pstring serial_number; // As %08X (not decimal) /* 1153C */ pstring access_key; @@ -378,7 +281,7 @@ struct PSOGCCharacterFile { /* 11564 */ be_uint32_t save_count; /* 11568 */ be_uint32_t round2_seed; /* 1156C */ -} __attribute__((packed)); +} __packed_ws__(PSOGCCharacterFile, 0x1156C); struct PSOGCEp3CharacterFile { /* 00000 */ be_uint32_t checksum; // crc32 of this field (as 0) through end of struct @@ -408,22 +311,22 @@ struct PSOGCEp3CharacterFile { // 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; - /* 08CC:04B0 */ GuildCardGC guild_card; - /* 095C:0540 */ parray symbol_chats; - /* 0D7C:0960 */ parray chat_shortcuts; - /* 140C:0FF0 */ pstring auto_reply; - /* 14B8:109C */ pstring info_board; + /* 086C:0450 */ parray bank_items; + /* 08CC:04B0 */ GuildCardGCBE guild_card; + /* 095C:0540 */ parray symbol_chats; + /* 0D7C:0960 */ parray chat_shortcuts; + /* 140C:0FF0 */ pstring auto_reply; + /* 14B8:109C */ pstring info_board; // In this struct, place_counts[0] is win_count and [1] is loss_count - /* 1564:1148 */ PlayerRecords_Battle battle_records; + /* 1564:1148 */ PlayerRecordsBattleBE battle_records; /* 157C:1160 */ parray unknown_a10; - /* 1580:1164 */ PlayerRecordsV3_Challenge::Stats challenge_record_stats; + /* 1580:1164 */ PlayerRecordsChallengeV3BE::Stats challenge_record_stats; /* 1658:123C */ Episode3::PlayerConfig ep3_config; /* 39A8:358C */ be_uint32_t unknown_a11; /* 39AC:3590 */ be_uint32_t unknown_a12; /* 39B0:3594 */ be_uint32_t unknown_a13; /* 39B4:3598 */ - } __attribute__((packed)); + } __packed_ws__(Character, 0x39B4); /* 00004 */ parray characters; /* 193F0 */ pstring serial_number; // As %08X (not decimal) /* 19400 */ pstring access_key; // As 12 ASCII characters (decimal) @@ -446,39 +349,26 @@ struct PSOGCEp3CharacterFile { /* 1942C */ parray card_rank_override_flags; /* 194AC */ be_uint32_t round2_seed; /* 194B0 */ -} __attribute__((packed)); +} __packed_ws__(PSOGCEp3CharacterFile, 0x194B0); struct PSOGCGuildCardFile { /* 0000 */ be_uint32_t checksum; /* 0004 */ parray unknown_a1; - struct GuildCardBE { - // Note: This struct (up through offset 0x90) is identical to GuildCardGC - // except for the 32-bit fields, which are big-endian here. - /* 0000 */ be_uint32_t player_tag; // == 0x00000001 (not 0x00010000) - /* 0004 */ be_uint32_t guild_card_number; - /* 0008 */ pstring name; - /* 0020 */ pstring description; - /* 008C */ uint8_t present; - /* 008D */ uint8_t language; - /* 008E */ uint8_t section_id; - /* 008F */ uint8_t char_class; - /* 0090 */ - } __attribute__((packed)); struct GuildCardEntry { - /* 0000 */ GuildCardBE base; + /* 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 */ - } __attribute__((packed)); + } __packed_ws__(GuildCardEntry, 0x100); /* 00C4 */ parray entries; - /* D2C4 */ parray blocked_senders; + /* D2C4 */ parray blocked_senders; /* E284 */ be_uint32_t creation_timestamp; /* E288 */ be_uint32_t round2_seed; /* E28C */ -} __attribute__((packed)); +} __packed_ws__(PSOGCGuildCardFile, 0xE28C); struct PSOGCSnapshotFile { /* 00000 */ be_uint32_t checksum; @@ -497,7 +387,7 @@ struct PSOGCSnapshotFile { bool checksum_correct() const; Image decode_image() const; -} __attribute__((packed)); +} __packed_ws__(PSOGCSnapshotFile, 0x1818C); template std::string decrypt_data_section(const void* data_section, size_t size, uint32_t round1_seed, size_t max_decrypt_bytes = 0) { @@ -681,7 +571,7 @@ struct PSOPCCreationTimeFile { // PSO______FLS /* 0624 */ le_uint32_t creation_timestamp; /* 0628 */ parray unused2; /* 1400 */ -} __attribute__((packed)); +} __packed_ws__(PSOPCCreationTimeFile, 0x1400); struct PSOPCSystemFile { // PSO______COM /* 0000 */ le_uint32_t checksum; @@ -696,7 +586,7 @@ struct PSOPCSystemFile { // PSO______COM /* 012C */ le_uint32_t round1_seed; /* 0130 */ parray end_padding; /* 0200 */ -} __attribute__((packed)); +} __packed_ws__(PSOPCSystemFile, 0x200); struct PSOPCGuildCardFile { // PSO______GUD /* 0000 */ le_uint32_t checksum; @@ -706,7 +596,7 @@ struct PSOPCGuildCardFile { // PSO______GUD /* 7988 */ le_uint32_t round2_seed; /* 798C */ parray end_padding; /* 7A00 */ -} __attribute__((packed)); +} __packed_ws__(PSOPCGuildCardFile, 0x7A00); struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD /* 00000 */ le_uint32_t signature; // 'CAEN' (stored as 4E 45 41 43) @@ -729,7 +619,7 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD // 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; + /* 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; @@ -739,14 +629,168 @@ struct PSOPCCharacterFile { // PSO______SYS and PSO______SYD /* 1D40 */ pstring access_key; // As decimal /* 1D50 */ le_uint32_t round2_seed; /* 1D54 */ - } __attribute__((packed)); + } __packed_ws__(Character, 0x1D54); /* 0004 */ Character character; /* 1D58 */ parray unused; /* 1D94 */ - } __attribute__((packed)); + } __packed_ws__(CharacterEntry, 0x1D94); /* 00440 */ parray entries; /* ECE40 */ -} __attribute__((packed)); +} __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 */ PlayerBank 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_gc(const PSOGCCharacterFile::Character& gc_char); + + 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; +} __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. @@ -756,11 +800,11 @@ struct LegacySavedPlayerDataBB { // .nsc file format /* 0000 */ be_uint64_t signature = SIGNATURE_V1; /* 0008 */ parray unused; - /* 0028 */ PlayerRecords_Battle battle_records; + /* 0028 */ PlayerRecordsBattle battle_records; /* 0040 */ PlayerDispDataBBPreview preview; /* 00BC */ pstring auto_reply; /* 0214 */ PlayerBank bank; - /* 14DC */ PlayerRecordsBB_Challenge challenge_records; + /* 14DC */ PlayerRecordsChallengeBB challenge_records; /* 161C */ PlayerDispDataBB disp; /* 17AC */ pstring guild_card_description; /* 185C */ pstring info_board; @@ -769,9 +813,9 @@ struct LegacySavedPlayerDataBB { // .nsc file format /* 1D04 */ QuestFlags quest_flags; /* 1F04 */ le_uint32_t death_count; /* 1F08 */ parray quest_counters; - /* 1F60 */ parray tech_menu_config; + /* 1F60 */ parray tech_menu_shortcut_entries; /* 1F88 */ -} __attribute__((packed)); +} __packed_ws__(LegacySavedPlayerDataBB, 0x1F88); // This format is specific to newserv and is no longer used, but remains here // for backward compatibility. @@ -784,10 +828,10 @@ struct LegacySavedAccountDataBB { // .nsa file format /* D648 */ PSOBBFullSystemFile system_file; /* E138 */ le_uint32_t unused; /* E13C */ le_uint32_t option_flags; - /* E140 */ parray shortcuts; - /* EB80 */ parray symbol_chats; + /* E140 */ parray shortcuts; + /* EB80 */ parray symbol_chats; /* F060 */ pstring team_name; /* F080 */ -} __attribute__((packed)); +} __packed_ws__(LegacySavedAccountDataBB, 0xF080); std::string encode_psobb_hangame_credentials(const std::string& user_id, const std::string& token, const std::string& unused = ""); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 230ba2a0..ce6f5a32 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -155,11 +155,11 @@ static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyrigh static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM."; static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM."; -S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4> +S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4> prepare_server_init_contents_console( uint32_t server_key, uint32_t client_key, uint8_t flags) { bool initial_connection = (flags & SendServerInitFlag::IS_INITIAL_CONNECTION); - S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4> cmd; + S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4> cmd; cmd.basic_cmd.copyright.encode(initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright); cmd.basic_cmd.server_key = server_key; cmd.basic_cmd.client_key = client_key; @@ -205,13 +205,13 @@ void send_server_init_dc_pc_v3(shared_ptr c, uint8_t flags) { } } -S_ServerInitWithAfterMessage_BB_03_9B<0xB4> +S_ServerInitWithAfterMessageT_BB_03_9B<0xB4> prepare_server_init_contents_bb( const parray& server_key, const parray& client_key, uint8_t flags) { bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE); - S_ServerInitWithAfterMessage_BB_03_9B<0xB4> cmd; + S_ServerInitWithAfterMessageT_BB_03_9B<0xB4> cmd; cmd.basic_cmd.copyright.encode(use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright); cmd.basic_cmd.server_key = server_key; cmd.basic_cmd.client_key = client_key; @@ -534,7 +534,7 @@ bool send_protected_command(std::shared_ptr c, const void* data, size_t } void send_reconnect(shared_ptr c, uint32_t address, uint16_t port) { - S_Reconnect_19 cmd = {{address, port, 0}}; + S_Reconnect_19 cmd = {address, port, 0}; send_command_t(c, is_patch(c->version()) ? 0x14 : 0x19, 0x00, cmd); } @@ -1056,7 +1056,7 @@ void send_simple_mail(shared_ptr s, uint32_t from_guild_card_number template void send_info_board_t(shared_ptr c) { - vector> entries; + vector> entries; auto l = c->require_lobby(); for (const auto& other_c : l->clients) { if (!other_c.get()) { @@ -1125,7 +1125,7 @@ void send_card_search_result_t( auto s = c->require_server_state(); string port_name = lobby_port_name_for_version(c->version()); - S_GuildCardSearchResult cmd; + S_GuildCardSearchResultT cmd; cmd.player_tag = 0x00010000; cmd.searcher_guild_card_number = c->license->serial_number; cmd.result_guild_card_number = result->license->serial_number; @@ -1413,7 +1413,7 @@ void send_game_menu_t( bool show_tournaments_only) { auto s = c->require_server_state(); - vector> entries; + vector> entries; { auto& e = entries.emplace_back(); e.menu_id = MenuID::GAME; @@ -1770,8 +1770,8 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto& p = cmd.players[z]; populate_lobby_data_for_client(p.lobby_data, wc, c); p.inventory = wc_p->inventory; - p.inventory.encode_for_client(c); - p.disp = wc_p->disp.to_dcpcv3(c->language(), p.inventory.language); + p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version())); + p.disp = wc_p->disp.to_dcpcv3(c->language(), p.inventory.language); p.disp.enforce_lobby_join_limits_for_version(c->version()); auto& e = cmd.entries[z]; @@ -1813,7 +1813,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto& p = cmd.players[client_id]; p.lobby_data = entry.lobby_data; p.inventory = entry.inventory; - p.inventory.encode_for_client(c); + p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version())); p.disp = entry.disp; p.disp.enforce_lobby_join_limits_for_version(c->version()); @@ -1840,7 +1840,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto& cmd_e = cmd.entries[z]; populate_lobby_data_for_client(cmd_p.lobby_data, other_c, c); cmd_p.inventory = other_p->inventory; - cmd_p.disp = other_p->disp.to_dcpcv3(c->language(), cmd_p.inventory.language); + cmd_p.disp = other_p->disp.to_dcpcv3(c->language(), cmd_p.inventory.language); cmd_p.disp.enforce_lobby_join_limits_for_version(c->version()); cmd_e.player_tag = 0x00010000; @@ -1960,7 +1960,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { auto other_p = l->clients[x]->character(); auto& cmd_p = cmd.players_ep3[x]; cmd_p.inventory = other_p->inventory; - cmd_p.inventory.encode_for_client(c); + cmd_p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version())); cmd_p.disp = convert_player_disp_data(other_p->disp, c->language(), other_p->inventory.language); cmd_p.disp.enforce_lobby_join_limits_for_version(c->version()); if (s->version_name_colors) { @@ -2048,7 +2048,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrblock; } - S_JoinLobby cmd; + S_JoinLobbyT cmd; cmd.lobby_flags.client_id = c->lobby_client_id; cmd.lobby_flags.leader_id = l->leader_id; cmd.lobby_flags.disable_udp = 0x01; @@ -2076,7 +2076,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, shared_ptrinventory; - e.inventory.encode_for_client(c); + e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version())); if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) { e.disp = convert_player_disp_data(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language); } else { @@ -2151,7 +2151,7 @@ void send_join_lobby_xb(shared_ptr c, shared_ptr l, shared_ptrinventory; - e.inventory.encode_for_client(c); + e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version())); e.disp = convert_player_disp_data(lp->disp, c->language(), lp->inventory.language); e.disp.enforce_lobby_join_limits_for_version(c->version()); if (s->version_name_colors) { @@ -2199,7 +2199,7 @@ void send_join_lobby_dc_nte(shared_ptr c, shared_ptr l, auto& e = cmd.entries[used_entries++]; populate_lobby_data_for_client(e.lobby_data, lc, c); e.inventory = lp->inventory; - e.inventory.encode_for_client(c); + e.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version())); if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) { e.disp = convert_player_disp_data(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language); } else { @@ -2319,8 +2319,39 @@ void send_self_leave_notification(shared_ptr c) { send_command_t(c, 0x69, c->lobby_client_id, cmd); } -void send_get_player_info(shared_ptr c) { - send_command(c, (c->version() == Version::DC_NTE) ? 0x8D : 0x95, 0x00); +static bool send_get_extended_player_info(shared_ptr c) { + // TODO: Support extended player info on other versions. + if (c->version() != Version::GC_V3) { + return false; + } + auto s = c->require_server_state(); + if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL) || + c->config.check_flag(Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY)) { + return false; + } + + prepare_client_for_patches(c, [wc = weak_ptr(c)]() { + auto c = wc.lock(); + if (!c) { + return; + } + try { + auto s = c->require_server_state(); + auto fn = s->function_code_index->get_patch("GetExtendedPlayerInfo", c->config.specific_version); + send_function_call(c, fn); + c->function_call_response_queue.emplace_back(empty_function_call_response_handler); + } catch (const exception& e) { + c->log.warning("Failed to send extended player info request: %s", e.what()); + send_get_player_info(c, false); + } + }); + return true; +} + +void send_get_player_info(shared_ptr c, bool request_extended) { + if (!request_extended || !send_get_extended_player_info(c)) { + send_command(c, (c->version() == Version::DC_NTE) ? 0x8D : 0x95, 0x00); + } } //////////////////////////////////////////////////////////////////////////////// @@ -3151,7 +3182,7 @@ template void send_ep3_tournament_details_t( shared_ptr c, shared_ptr tourn) { - S_TournamentGameDetailsBase_Ep3_E3 cmd; + S_TournamentGameDetailsBaseT_Ep3_E3 cmd; auto vm = tourn->get_map()->version(c->language()); cmd.name.encode(tourn->get_name(), c->language()); cmd.map_name.encode(vm->map->name.decode(vm->language), c->language()); @@ -3203,7 +3234,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { auto tourn = tourn_match ? tourn_match->tournament.lock() : nullptr; if (tourn) { - S_TournamentGameDetailsBase_Ep3_E3 cmd; + S_TournamentGameDetailsBaseT_Ep3_E3 cmd; cmd.name.encode(l->name, c->language()); auto vm = tourn->get_map()->version(c->language()); @@ -3222,7 +3253,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { if (primary_lobby) { auto serial_number_to_client = primary_lobby->clients_by_serial_number(); - using TeamEntryT = typename S_TournamentGameDetailsBase_Ep3_E3::TeamEntry; + using TeamEntryT = typename S_TournamentGameDetailsBaseT_Ep3_E3::TeamEntry; auto describe_team = [&](TeamEntryT& team_entry, shared_ptr team) -> void { team_entry.team_name.encode(team->name, c->language()); for (size_t z = 0; z < team->players.size(); z++) { @@ -3263,7 +3294,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { send_command_t(c, 0xE3, flag, cmd); } else { - S_GameInformationBase_Ep3_E1 cmd; + S_GameInformationBaseT_Ep3_E1 cmd; cmd.game_name.encode(l->name, c->language()); if (primary_lobby) { size_t num_players = 0; @@ -3292,7 +3323,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { // spectator count in the info window object. To account for this, we send // a mostly-blank E3 to set the spectator count, followed by an E1 with // the correct data. - S_TournamentGameDetailsBase_Ep3_E3 cmd_E3; + S_TournamentGameDetailsBaseT_Ep3_E3 cmd_E3; cmd_E3.num_spectators = num_spectators; send_command_t(c, 0xE3, 0x04, cmd_E3); diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 73ad33f3..584be9f8 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -119,10 +119,10 @@ enum SendServerInitFlag { USE_SECONDARY_MESSAGE = 0x02, }; -S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4> +S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B<0xB4> prepare_server_init_contents_console( uint32_t server_key, uint32_t client_key, uint8_t flags); -S_ServerInitWithAfterMessage_BB_03_9B<0xB4> +S_ServerInitWithAfterMessageT_BB_03_9B<0xB4> prepare_server_init_contents_bb( const parray& server_key, const parray& client_key, @@ -287,7 +287,7 @@ void send_update_lobby_data_bb(std::shared_ptr c); void send_player_join_notification(std::shared_ptr c, std::shared_ptr l, std::shared_ptr joining_client); void send_player_leave_notification(std::shared_ptr l, uint8_t leaving_client_id); void send_self_leave_notification(std::shared_ptr c); -void send_get_player_info(std::shared_ptr c); +void send_get_player_info(std::shared_ptr c, bool request_extended = false); void send_execute_item_trade(std::shared_ptr c, const std::vector& items); void send_execute_card_trade(std::shared_ptr c, const std::vector>& card_to_count); diff --git a/src/Text.hh b/src/Text.hh index 1b44af28..923e9750 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -11,6 +11,15 @@ #include #include +#define __packed__ __attribute__((packed)) +#define check_struct_size(StructT, Size) \ + static_assert(sizeof(StructT) >= Size, "Structure size is too small"); \ + static_assert(sizeof(StructT) <= Size, "Structure size is too large") + +#define __packed_ws__(StructT, Size) \ + __packed__; \ + check_struct_size(StructT, Size) + // Conversion functions class TextTranscoder { @@ -112,6 +121,12 @@ struct parray { this->operator=(std::move(other)); } + template + requires std::is_convertible_v + parray(const parray& other) { + this->operator=(other); + } + template parray(const parray& s) { this->operator=(s); @@ -203,6 +218,18 @@ struct parray { return *this; } + template + requires std::is_convertible_v + parray& operator=(const parray& s) { + for (size_t x = 0; x < Count; x++) { + const FromT& src_item = s.items[x]; + ItemT& dest_item = this->items[x]; + static_assert(!std::is_const_v, "ItemT is const"); + dest_item = src_item; + } + return *this; + } + template parray& operator=(const parray& s) { if (OtherCount <= Count) { @@ -267,7 +294,7 @@ struct parray { } return true; } -} __attribute__((packed)); +} __packed__; template struct bcarray { @@ -672,7 +699,7 @@ struct pstring { // Note: The contents of a pstring do not have to be null-terminated, so there // is no .c_str() function. -} __attribute__((packed)); +} __packed__; // Helper functions diff --git a/src/WordSelectTable.cc b/src/WordSelectTable.cc index 20b069be..e5eb4e09 100644 --- a/src/WordSelectTable.cc +++ b/src/WordSelectTable.cc @@ -32,7 +32,7 @@ static vector> read_indirect_table(const StringReader& base_r, size } template -struct NonWindowsRoot { +struct NonWindowsRootT { using U32T = typename std::conditional::type; U32T strings_table; U32T table1; @@ -41,7 +41,12 @@ struct NonWindowsRoot { U32T table4; U32T article_types_table; U32T table6; -} __attribute__((packed)); +} __packed__; + +using NonWindowsRoot = NonWindowsRootT; +using NonWindowsRootBE = NonWindowsRootT; +check_struct_size(NonWindowsRoot, 0x1C); +check_struct_size(NonWindowsRootBE, 0x1C); struct PCV2Root { le_uint32_t unknown_a1; @@ -52,7 +57,7 @@ struct PCV2Root { le_uint32_t table4; le_uint32_t article_types_table; le_uint32_t table6; -} __attribute__((packed)); +} __packed_ws__(PCV2Root, 0x20); struct BBRoot { le_uint32_t table1; @@ -61,7 +66,7 @@ struct BBRoot { le_uint32_t table4; le_uint32_t article_types_table; le_uint32_t table6; -} __attribute__((packed)); +} __packed_ws__(BBRoot, 0x18); template void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) { @@ -70,7 +75,7 @@ void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) StringReader r(data); uint32_t root_offset = r.pget(r.size() - 0x10); - const auto& root = r.pget>(root_offset); + const auto& root = r.pget>(root_offset); { auto string_offset_r = r.sub(root.strings_table, sizeof(U32T) * StringTableCount); diff --git a/system/client-functions/CopyDataWords.ppc.inc.s b/system/client-functions/CopyDataWords.ppc.inc.s new file mode 100644 index 00000000..4c4936c5 --- /dev/null +++ b/system/client-functions/CopyDataWords.ppc.inc.s @@ -0,0 +1,13 @@ + # r3 = dest ptr + # r4 = src ptr + # r5 = size + # Clobbers r3, r4, r5 + addi r5, r5, 3 + rlwinm r5, r5, 30, 2, 31 # r5 = number of words to copy + mtctr r5 + subi r3, r3, 4 # r3 = r3 - 4 (so we can use stwu) + subi r4, r4, 4 # r4 = r4 - 4 (so we can use lwzu) +copy_word_again: + lwzu r5, [r4 + 4] + stwu [r3 + 4], r5 + bdnz copy_word_again diff --git a/system/client-functions/GetExtendedPlayerInfo.3OE0.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OE0.patch.s new file mode 100644 index 00000000..40e996a5 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OE0.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DB0E0 # malloc9 + .data 0x802021D0 # get_character_file + .data 0x802021AC # get_selected_character_file_index + .data 0x805C4D80 # root_protocol (anchor: send_05) + .data 0x803DB138 # free9 + .data 0x800787B0 # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OE1.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OE1.patch.s new file mode 100644 index 00000000..48aa2c31 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OE1.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DB138 # malloc9 + .data 0x802021D0 # get_character_file + .data 0x802021AC # get_selected_character_file_index + .data 0x805CBD60 # root_protocol (anchor: send_05) + .data 0x803DB190 # free9 + .data 0x800787B0 # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OE2.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OE2.patch.s new file mode 100644 index 00000000..0320675d --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OE2.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DE838 # malloc9 + .data 0x80202BA0 # get_character_file + .data 0x80202B7C # get_selected_character_file_index + .data 0x805D5580 # root_protocol (anchor: send_05) + .data 0x803DE890 # free9 + .data 0x8007889C # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OJ2.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OJ2.patch.s new file mode 100644 index 00000000..50cccdb4 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OJ2.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803D9E38 # malloc9 + .data 0x802019D4 # get_character_file + .data 0x802019B0 # get_selected_character_file_index + .data 0x805C4488 # root_protocol (anchor: send_05) + .data 0x803D9E90 # free9 + .data 0x8007848C # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OJ3.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OJ3.patch.s new file mode 100644 index 00000000..dd983269 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OJ3.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DC818 # malloc9 + .data 0x80202248 # get_character_file + .data 0x80202224 # get_selected_character_file_index + .data 0x805CEA50 # root_protocol (anchor: send_05) + .data 0x803DC870 # free9 + .data 0x800785F0 # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OJ4.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OJ4.patch.s new file mode 100644 index 00000000..2238b477 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OJ4.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DE6B8 # malloc9 + .data 0x801FD950 # get_character_file + .data 0x80222C0C # get_selected_character_file_index + .data 0x805D5ED0 # root_protocol (anchor: send_05) + .data 0x803DE710 # free9 + .data 0x80078748 # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OJ5.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OJ5.patch.s new file mode 100644 index 00000000..f07564f1 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OJ5.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DE468 # malloc9 + .data 0x8020286C # get_character_file + .data 0x80202848 # get_selected_character_file_index + .data 0x805D5C70 # root_protocol (anchor: send_05) + .data 0x803DE4C0 # free9 + .data 0x800786A0 # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfo.3OP0.patch.s b/system/client-functions/GetExtendedPlayerInfo.3OP0.patch.s new file mode 100644 index 00000000..09ee3479 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfo.3OP0.patch.s @@ -0,0 +1,19 @@ +# .meta hide_from_patches_menu +.meta name="GetExtendedPlayerInfo" +.meta description="" + +entry_ptr: +reloc0: + .offsetof start +start: + mflr r12 + bl get_data_addr +data: + .data 0x803DD328 # malloc9 + .data 0x80202AB4 # get_character_file + .data 0x80202A90 # get_selected_character_file_index + .data 0x805D17C0 # root_protocol (anchor: send_05) + .data 0x803DD380 # free9 + .data 0x80078820 # TProtocol_wait_send_drain +get_data_addr: + .include GetExtendedPlayerInfoGC diff --git a/system/client-functions/GetExtendedPlayerInfoGC.ppc.inc.s b/system/client-functions/GetExtendedPlayerInfoGC.ppc.inc.s new file mode 100644 index 00000000..17e54151 --- /dev/null +++ b/system/client-functions/GetExtendedPlayerInfoGC.ppc.inc.s @@ -0,0 +1,81 @@ + stwu [r1 - 0x20], r1 + stw [r1 + 0x24], r12 + stw [r1 + 0x08], r31 + stw [r1 + 0x0C], r30 + stw [r1 + 0x10], r29 + stw [r1 + 0x14], r28 + mflr r30 + + li r3, 0x279C + lwz r0, [r30] + mtctr r0 + bctrl # malloc9 + mr. r31, r3 + beq malloc9_failed + + lis r0, 0x3000 + ori r0, r0, 0x9C27 + stw [r31], r0 # header = 30 00 9C 27 + + lwz r0, [r30 + 0x04] + mtctr r0 + bctrl # get_character_file + mr r28, r3 + + lwz r0, [r30 + 0x08] + mtctr r0 + bctrl # get_selected_character_file_index + mulli r3, r3, 0x2798 + addi r3, r3, 4 + add r4, r3, r28 # r4 = &character_file->characters[selected_char_file_index] + addi r3, r31, 4 + li r5, 0x2798 + bl memcpy + + mr r28, r31 + li r29, 0x279C +send_again: + lwz r3, [r30 + 0x0C] + lwz r0, [r30 + 0x14] + mtctr r0 + bctrl # TProtocol_wait_send_drain(root_protocol) + mr. r0, r3 + bne drain_failed + + lwz r3, [r30 + 0x0C] + lwz r3, [r3] # root_protocol + mr r4, r28 + mr r5, r29 + cmplwi r5, 0x05B4 + ble skip_adjust_size + li r5, 0x05B4 +skip_adjust_size: + add r28, r28, r5 + sub r29, r29, r5 + lwz r12, [r3 + 0x18] + lwz r12, [r12 + 0x28] + mtctr r12 + bctrl # root_protocol->send(&cmd, sizeof(cmd)) + cmplwi r29, 0 + bne send_again + +drain_failed: + mr r3, r31 + lwz r0, [r30 + 0x10] + mtctr r0 + bctrl # free9 + li r3, 1 + +malloc9_failed: + lwz r28, [r1 + 0x14] + lwz r29, [r1 + 0x10] + lwz r30, [r1 + 0x0C] + lwz r31, [r1 + 0x08] + lwz r0, [r1 + 0x24] + addi r1, r1, 0x20 + mtlr r0 + blr + +memcpy: + .include CopyDataWords + blr