diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 67feb674..9e4a366c 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -402,7 +402,8 @@ static void command_lobby_info(shared_ptr, shared_ptr l, level_string = string_printf("Levels: %d-%d", l->min_level + 1, l->max_level + 1); } - send_text_message_printf(c, "$C6Game ID: %08X\n%s\nSection ID: %s\nCheat mode: %s", + send_text_message_printf(c, + "$C6Game ID: %08X\n%s\nSection ID: %s\nCheat mode: %s", l->lobby_id, level_string.c_str(), name_for_section_id(l->section_id).c_str(), (l->flags & LobbyFlag::CHEATS_ENABLED) ? "on" : "off"); @@ -547,7 +548,7 @@ static void command_password(shared_ptr, shared_ptr l, send_text_message(l, u"$C6Game unlocked"); } else { - strncpy_t(l->password, args, countof(l->password)); + l->password = args; auto encoded = encode_sjis(l->password); send_text_message_printf(l, "$C6Game password:\n%s", encoded.c_str()); @@ -619,7 +620,9 @@ static void command_edit(shared_ptr s, shared_ptr l, } else if (tokens[0] == "level") { c->player.disp.level = stoul(tokens[1]) - 1; } else if (tokens[0] == "namecolor") { - sscanf(tokens[1].c_str(), "%8X", &c->player.disp.name_color); + uint32_t new_color; + sscanf(tokens[1].c_str(), "%8X", &new_color); + c->player.disp.name_color = new_color; } else if (tokens[0] == "secid") { uint8_t secid = section_id_for_name(decode_sjis(tokens[1])); if (secid == 0xFF) { @@ -629,8 +632,7 @@ static void command_edit(shared_ptr s, shared_ptr l, c->player.disp.section_id = secid; } } else if (tokens[0] == "name") { - decode_sjis(c->player.disp.name, tokens[1].c_str(), 0x10); - add_language_marker_inplace(c->player.disp.name, u'J', 0x10); + c->player.disp.name = add_language_marker(tokens[1], 'J'); } else if (tokens[0] == "npc") { if (tokens[1] == "none") { c->player.disp.extra_model = 0; @@ -648,7 +650,7 @@ static void command_edit(shared_ptr s, shared_ptr l, uint8_t level = stoul(tokens[2]) - 1; if (tokens[1] == "all") { for (size_t x = 0; x < 0x14; x++) { - c->player.disp.technique_levels[x] = level; + c->player.disp.technique_levels.data()[x] = level; } } else { uint8_t tech_id = technique_for_name(decode_sjis(tokens[1])); @@ -656,7 +658,7 @@ static void command_edit(shared_ptr s, shared_ptr l, send_text_message(c, u"$C6No such technique."); return; } - c->player.disp.technique_levels[tech_id] = level; + c->player.disp.technique_levels.data()[tech_id] = level; } } else { send_text_message(c, u"$C6Unknown field."); diff --git a/src/Client.cc b/src/Client.cc index 8da09741..674e6897 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -63,8 +63,8 @@ ClientConfig Client::export_config() const { cc.flags = this->flags; cc.proxy_destination_address = this->proxy_destination_address; cc.proxy_destination_port = this->proxy_destination_port; - memset(cc.unused, 0xFF, sizeof(cc.unused)); - memset(cc.unused_bb_only, 0xFF, sizeof(cc.unused_bb_only)); + cc.unused.clear(0xFF); + cc.unused_bb_only.clear(0xFF); return cc; } diff --git a/src/Client.hh b/src/Client.hh index a539974a..904771d3 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -8,6 +8,8 @@ #include "Player.hh" #include "PSOEncryption.hh" +#include "Text.hh" + extern const uint64_t CLIENT_CONFIG_MAGIC; @@ -30,8 +32,8 @@ struct ClientConfig { uint16_t flags; uint32_t proxy_destination_address; uint16_t proxy_destination_port; - uint8_t unused[0x0E]; - uint8_t unused_bb_only[0x08]; + parray unused; + parray unused_bb_only; } __attribute__((packed)); struct Client { diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 72059c9b..ca2510c1 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -33,14 +33,14 @@ struct SC_TextHeader_01_06_11 { // Client will respond with an (encrypted) 9A, 9D, or 9E command struct S_ServerInit_DC_GC_02_17 { - char copyright[0x40]; + ptext copyright; le_uint32_t server_key; // Key for data sent by server le_uint32_t client_key; // Key for data sent by client - char after_message[200]; + ptext after_message; }; struct S_ServerInit_Patch_02 { - char copyright[0x40]; + ptext copyright; le_uint32_t server_key; le_uint32_t client_key; // BB rejects the command if it's not exactly this size, so we can't add the @@ -51,10 +51,10 @@ struct S_ServerInit_Patch_02 { // Client will respond with a 93 command struct S_ServerInit_BB_03 { - char copyright[0x60]; - uint8_t server_key[0x30]; - uint8_t client_key[0x30]; - char after_message[200]; + ptext copyright; + parray server_key; + parray client_key; + ptext after_message; }; // 04 (S->C): Set guild card number and update client config ("security data") @@ -74,9 +74,9 @@ struct S_UpdateClientConfig_DC_PC_GC_04 { // 04 (C->S): Log in (patch server) struct C_Login_Patch_04 { - le_uint32_t unused[3]; - char username[0x10]; - char password[0x10]; + parray unused; + ptext username; + ptext password; }; // 05: Disconnect @@ -89,7 +89,7 @@ struct C_Login_Patch_04 { // The guild_card_number field is used only in server->client commands struct C_Chat_06 { - le_uint32_t unused[2]; + uint64_t unused; union { char dcgc[0]; char16_t pcbb[0]; @@ -107,7 +107,7 @@ struct S_MenuEntry { le_uint32_t menu_id; le_uint32_t item_id; le_uint16_t flags; // should be 0x0F04 - CharT text[EntryLength]; + ptext text; }; struct S_MenuEntry_PC_BB_07 : S_MenuEntry { }; struct S_MenuEntry_DC_GC_07 : S_MenuEntry { }; @@ -126,7 +126,7 @@ struct S_GameMenuEntry { le_uint32_t game_id; uint8_t difficulty_tag; // 0x0A = Ep3; else difficulty + 0x22 (so 0x25 = Ult) uint8_t num_players; - CharT name[0x10]; + ptext name; uint8_t episode; // 40 = Ep1, 41 = Ep2, 43 = Ep4. Ignored on Ep3 uint8_t flags; // 02 = locked, 04 = disabled (BB), 10 = battle, 20 = challenge }; @@ -136,7 +136,7 @@ struct S_GameMenuEntry_GC_08 : S_GameMenuEntry { }; // 09 (S->C): Check directory (patch server) struct S_CheckDirectory_Patch_09 { - char name[0x40]; + ptext name; }; // 09 (C->S): Menu item info request @@ -189,7 +189,7 @@ struct C_MenuSelection { // Header flag = file chunk index struct S_WriteFile_13_A7 { - char filename[0x10]; + ptext filename; uint8_t data[0x400]; le_uint32_t data_size; }; @@ -220,13 +220,13 @@ struct S_Reconnect_19 { struct S_ReconnectSplit_19 { be_uint32_t pc_address; le_uint16_t pc_port; - uint8_t unused1[0x0F]; + parray unused1; uint8_t gc_command; uint8_t gc_flag; le_uint16_t gc_size; be_uint32_t gc_address; le_uint16_t gc_port; - uint8_t unused2[0xB0 - 0x23]; + parray unused2; }; // 1A: Large message box @@ -256,7 +256,7 @@ struct S_ReconnectSplit_19 { // command had no payload). The latest version uses this 16-byte challenge. struct SC_GameCardCheck_BB_22 { - uint8_t data[0x10]; + parray data; }; // 23: Invalid command @@ -304,11 +304,11 @@ struct S_GuildCardSearchResult { le_uint32_t result_serial_number; HeaderT reconnect_command_header; S_Reconnect_19 reconnect_command; - char location_string[0x44]; + ptext location_string; le_uint32_t menu_id; le_uint32_t lobby_id; - char unused[0x3C]; - CharT name[0x20]; + ptext unused; + ptext name; }; struct S_GuildCardSearchResult_PC_40 : S_GuildCardSearchResult { }; struct S_GuildCardSearchResult_DC_GC_40 : S_GuildCardSearchResult { }; @@ -326,19 +326,19 @@ struct S_GuildCardSearchResult_BB_40 : S_GuildCardSearchResult name; le_uint16_t unused; le_uint16_t flags; - char filename[0x10]; + ptext filename; le_uint32_t file_size; }; struct S_OpenFile_BB_44_A6 { - uint8_t unused[0x22]; + parray unused; le_uint16_t flags; - char filename[0x10]; + ptext filename; le_uint32_t file_size; - char name[0x18]; + ptext name; }; // 45: Invalid command @@ -391,7 +391,7 @@ struct S_OpenFile_BB_44_A6 { // Header flag = entry count template struct S_JoinGame { - le_uint32_t variations[0x20]; + parray variations; // Unlike lobby join commands, these are filled in in their slot positions. // That is, if there's one player in a game with ID 2, then the first two of // these are blank and the player's data is in the third entry here. @@ -514,9 +514,9 @@ struct S_LeaveLobby_66_69 { struct SC_SimpleMail_GC_81 { le_uint32_t player_tag; le_uint32_t from_serial_number; - char from_name[0x10]; + ptext from_name; le_uint32_t to_serial_number; - char text[0x200]; + ptext text; }; // 82: Invalid command @@ -574,11 +574,11 @@ struct S_ArrowUpdateEntry_88 { // 93 (C->S): Log in (BB) struct C_Login_BB_93 { - char unused[0x14]; - char username[0x10]; - char unused2[0x20]; - char password[0x10]; - char unused3[0x30]; + ptext unused; + ptext username; + ptext unused2; + ptext password; + ptext unused3; ClientConfig cfg; }; @@ -611,9 +611,9 @@ struct C_ClientChecksum_GC_96 { // 9A (C->S): Initial login (no password or client config) struct C_Login_DC_PC_GC_9A { - char unused[0x20]; - char serial_number[0x10]; - char access_key[0x10]; + ptext unused; + ptext serial_number; + ptext access_key; }; // 9B: Invalid command @@ -625,12 +625,12 @@ struct C_Login_DC_PC_GC_9A { // 9C (C->S): Register struct C_Register_DC_PC_GC_9C { - char unused[8]; + ptext unused; le_uint32_t sub_version; le_uint32_t unused2; - char serial_number[0x30]; - char access_key[0x30]; - char password[0x30]; + ptext serial_number; + ptext access_key; + ptext password; }; // 9D (C->S): Log in with client config @@ -641,19 +641,19 @@ struct C_Register_DC_PC_GC_9C { struct C_Login_PC_GC_9D_9E { le_uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04) le_uint32_t guild_card_number; // FF FF FF FF if not set - le_uint32_t unused1[2]; + le_uint64_t unused; le_uint32_t sub_version; - uint8_t unused2[0x24]; // 00 01 00 00 ... (rest is 00) - char serial_number[0x10]; - char access_key[0x10]; - char serial_number2[0x30]; - char access_key2[0x30]; - char name[0x10]; + parray unused2; // 00 01 00 00 ... (rest is 00) + ptext serial_number; + ptext access_key; + ptext serial_number2; + ptext access_key2; + ptext name; // Note: there are 8 bytes at the end of cfg that are technically not // included in the client config on GC, but the field after it is // sufficiently large and unused anyway ClientConfig cfg; - uint8_t unused4[0x5C]; + parray unused4; }; // 9F: Invalid command @@ -679,8 +679,8 @@ template struct S_QuestMenuEntry { le_uint32_t menu_id; le_uint32_t item_id; - CharT name[0x20]; - CharT short_desc[0x70]; + ptext name; + ptext short_desc; }; struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry { }; struct S_QuestMenuEntry_GC_A2_A4 : S_QuestMenuEntry { }; @@ -688,8 +688,9 @@ struct S_QuestMenuEntry_GC_A2_A4 : S_QuestMenuEntry { }; struct S_QuestMenuEntry_BB_A2_A4 { le_uint32_t menu_id; le_uint32_t item_id; - char16_t name[0x20]; - char16_t short_desc[0x7A]; // Why is this not the same as PC? Mysteries of SEGA's engineering... + ptext name; + // Why is this 10 characters longer than on other versions...? + ptext short_desc; }; // A3 (S->C): Quest information @@ -755,7 +756,7 @@ struct S_QuestMenuEntry_BB_A2_A4 { struct S_RankUpdate_GC_Ep3_B7 { le_uint32_t rank; - char rank_text[0x0C]; + ptext rank_text; le_uint32_t meseta; le_uint32_t max_meseta; le_uint32_t jukebox_songs_unlocked; @@ -799,7 +800,7 @@ struct S_ChoiceSearchEntry { // be set by the user at any time; otherwise it can't. le_uint16_t parent_category_id; // 0 for top-level categories le_uint16_t category_id; - CharT text[0x1C]; + ptext text; }; struct S_ChoiceSearchEntry_DC_GC_C0 : S_ChoiceSearchEntry { }; struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { }; @@ -821,9 +822,9 @@ struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry { }; template struct C_CreateGame { - le_uint32_t unused[2]; - CharT name[0x10]; - CharT password[0x10]; + le_uint64_t unused; + ptext name; + ptext password; uint8_t difficulty; uint8_t battle_mode; uint8_t challenge_mode; @@ -831,6 +832,7 @@ struct C_CreateGame { }; struct C_CreateGame_DC_GC_C1_EC : C_CreateGame { }; struct C_CreateGame_PC_C1 : C_CreateGame { }; + struct C_CreateGame_BB_C1 : C_CreateGame { uint8_t solo_mode; uint8_t unused2[3]; @@ -864,12 +866,12 @@ struct C_ExecuteChoiceSearch_C3 { // Command is a list of these; header.flag is the entry count struct S_ChoiceSearchResultEntry_GC_C4 { le_uint32_t guild_card_number; - char name[0x10]; // No language marker, as usual on GC - char info_string[0x20]; // Usually something like " Lvl " + ptext name; // No language marker, as usual on GC + ptext info_string; // Usually something like " Lvl " // Format is stricter here; this is "LOBBYNAME,BLOCKNUM,SHIPNAME" // If target is in game, for example, "Game Name,BLOCK01,Alexandria" // If target is in lobby, for example, "BLOCK01-1,BLOCK01,Alexandria" - char locator_string[0x34]; + ptext locator_string; // Server IP and port for "meet user" option le_uint32_t server_ip; le_uint16_t server_port; @@ -877,7 +879,7 @@ struct S_ChoiceSearchResultEntry_GC_C4 { le_uint32_t menu_id; le_uint32_t lobby_id; // These two are guesses le_uint32_t game_id; // Zero if target is in a lobby rather than a game - uint8_t unused2[0x58]; + parray unused2; }; // C5 (S->C): Challenge rank update @@ -886,7 +888,7 @@ struct S_ChoiceSearchResultEntry_GC_C4 { // C6 (C->S): Set blocked senders list struct C_SetBlockedSenders_C6 { - le_uint32_t blocked_senders[30]; + parray blocked_senders; }; // C7 (C->S): Enable simple mail auto-reply @@ -936,7 +938,7 @@ struct C_SetBlockedSenders_C6 { // D7 (C->S): Request GBA game file struct C_GBAGameRequest_GC_D7 { - char filename[0x10]; + ptext filename; }; // D8 (S->C): Info board @@ -944,8 +946,8 @@ struct C_GBAGameRequest_GC_D7 { // Command is a list of these; header.flag is the entry count template struct S_InfoBoardEntry_D8 { - CharT name[0x10]; - CharT message[0xAC]; + ptext name; + ptext message; }; struct S_InfoBoardEntry_PC_BB_D8 : S_InfoBoardEntry_D8 { }; struct S_InfoBoardEntry_DC_GC_D8 : S_InfoBoardEntry_D8 { }; @@ -959,14 +961,14 @@ struct S_InfoBoardEntry_DC_GC_D8 : S_InfoBoardEntry_D8 { }; // DB (S->C): Verify license (GC) struct C_VerifyLicense_GC_DB { - char unused[0x20]; - char serial_number[0x10]; - char access_key[0x10]; - char unused2[0x08]; + ptext unused; + ptext serial_number; + ptext access_key; + ptext unused2; le_uint32_t sub_version; - char serial_number2[0x30]; - char access_key2[0x30]; - char password[0x30]; + ptext serial_number2; + ptext access_key2; + ptext password; }; // DC: Player menu state (Episode 3) @@ -1002,7 +1004,7 @@ struct S_GuildCardHeader_BB_01DC { struct S_TournamentEntry_GC_Ep3_E0 { le_uint32_t menu_id; le_uint32_t item_id; - uint8_t unknown[0x30]; + parray unknown; }; // E0 (C->S): Request team and key config (BB) @@ -1104,7 +1106,7 @@ struct S_StreamFileIndexEntry_BB_01EB { le_uint32_t size; le_uint32_t checksum; le_uint32_t offset; - char filename[0x40]; + ptext filename; } __attribute__((packed)); struct S_StreamFileChunk_BB_02EB { @@ -1123,12 +1125,12 @@ struct S_StreamFileChunk_BB_02EB { union C_UpdateAccountData_BB_ED { le_uint32_t option; // 01ED - uint8_t symbol_chats[0x4E0]; // 02ED - uint8_t chat_shortcuts[0xA40]; // 03ED - uint8_t key_config[0x16C]; // 04ED - uint8_t pad_config[0x38]; // 05ED - uint8_t tech_menu[0x28]; // 06ED - uint8_t customize[0xE8]; // 07ED + parray symbol_chats; // 02ED + parray chat_shortcuts; // 03ED + parray key_config; // 04ED + parray pad_config; // 05ED + parray tech_menu; // 06ED + parray customize; // 07ED } __attribute__((packed)); // EE: Scrolling message (BB) @@ -1162,8 +1164,8 @@ struct S_SendGuildCard_GC { le_uint16_t unused; le_uint32_t player_tag; le_uint32_t serial_number; - char name[0x18]; - char desc[0x6C]; + ptext name; + ptext desc; uint8_t reserved1; uint8_t reserved2; uint8_t section_id; @@ -1175,9 +1177,9 @@ struct S_SendGuildCard_BB { uint8_t subsize; le_uint16_t unused; le_uint32_t serial_number; - char16_t name[0x18]; - char16_t team_name[0x10]; - char16_t desc[0x58]; + ptext name; + ptext team_name; + ptext desc; uint8_t reserved1; uint8_t reserved2; uint8_t section_id; diff --git a/src/Items.cc b/src/Items.cc index 1607e768..a416e190 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -168,7 +168,7 @@ void player_use_item(shared_ptr c, size_t item_index) { auto& item = c->player.inventory.items[item_index]; if (item.data.item_data1w[0] == 0x0203) { // technique disk - c->player.disp.technique_levels[item.data.item_data1[4]] = item.data.item_data1[2]; + c->player.disp.technique_levels.data()[item.data.item_data1[4]] = item.data.item_data1[2]; } else if (item.data.item_data1w[0] == 0x0A03) { // grinder if (equipped_weapon < 0) { diff --git a/src/License.cc b/src/License.cc index 1a71c233..0e7cd368 100644 --- a/src/License.cc +++ b/src/License.cc @@ -11,31 +11,23 @@ using namespace std; -License::License() { - memset(this->username, 0, 20); - memset(this->bb_password, 0, 20); - this->serial_number = 0; - memset(this->access_key, 0, 16); - memset(this->gc_password, 0, 12); - this->privileges = 0; - this->ban_end_time = 0; -} +License::License() : serial_number(0), privileges(0), ban_end_time(0) { } string License::str() const { string ret = string_printf("License(serial_number=%" PRIu32, this->serial_number); - if (this->username[0]) { + if (!this->username.empty()) { ret += ", username="; ret += this->username; } - if (this->bb_password[0]) { + if (!this->bb_password.empty()) { ret += ", bb-password="; ret += this->bb_password; } - if (this->access_key[0]) { + if (!this->access_key.empty()) { ret += ", access-key="; ret += this->access_key; } - if (this->gc_password[0]) { + if (!this->gc_password.empty()) { ret += ", gc-password="; ret += this->gc_password; } @@ -83,10 +75,10 @@ void LicenseManager::save() const { shared_ptr LicenseManager::verify_pc(uint32_t serial_number, const char* access_key, const char* password) const { auto& license = this->serial_number_to_license.at(serial_number); - if (strncmp(license->access_key, access_key, 8)) { + if (!license->access_key.eq_n(access_key, 8)) { throw invalid_argument("incorrect access key"); } - if (password && (strcmp(license->gc_password, password))) { + if (password && (license->gc_password != password)) { throw invalid_argument("incorrect password"); } @@ -99,10 +91,10 @@ shared_ptr LicenseManager::verify_pc(uint32_t serial_number, shared_ptr LicenseManager::verify_gc(uint32_t serial_number, const char* access_key, const char* password) const { auto& license = this->serial_number_to_license.at(serial_number); - if (strncmp(license->access_key, access_key, 12)) { + if (!license->access_key.eq_n(access_key, 12)) { throw invalid_argument("incorrect access key"); } - if (password && (strcmp(license->gc_password, password))) { + if (password && (license->gc_password != password)) { throw invalid_argument("incorrect password"); } @@ -115,7 +107,7 @@ shared_ptr LicenseManager::verify_gc(uint32_t serial_number, shared_ptr LicenseManager::verify_bb(const char* username, const char* password) const { auto& license = this->bb_username_to_license.at(username); - if (password && strcmp(license->bb_password, password)) { + if (password && (license->bb_password != password)) { throw invalid_argument("incorrect password"); } @@ -137,20 +129,18 @@ void LicenseManager::ban_until(uint32_t serial_number, uint64_t end_time) { void LicenseManager::add(shared_ptr l) { uint32_t serial_number = l->serial_number; this->serial_number_to_license.emplace(serial_number, l); - if (l->username[0]) { + if (!l->username.empty()) { this->bb_username_to_license.emplace(l->username, l); } - this->save(); } void LicenseManager::remove(uint32_t serial_number) { auto l = this->serial_number_to_license.at(serial_number); this->serial_number_to_license.erase(l->serial_number); - if (l->username[0]) { + if (!l->username.empty()) { this->bb_username_to_license.erase(l->username); } - this->save(); } @@ -168,9 +158,9 @@ shared_ptr LicenseManager::create_license_pc( uint32_t serial_number,const char* access_key, const char* password, bool temporary) { shared_ptr l(new License()); l->serial_number = serial_number; - strncpy(l->access_key, access_key, 8); + l->access_key = access_key; if (password) { - strncpy(l->gc_password, password, 8); + l->gc_password = password; } if (temporary) { l->privileges |= Privilege::TEMPORARY; @@ -182,9 +172,9 @@ shared_ptr LicenseManager::create_license_gc( uint32_t serial_number, const char* access_key, const char* password, bool temporary) { shared_ptr l(new License()); l->serial_number = serial_number; - strncpy(l->access_key, access_key, 12); + l->access_key = access_key; if (password) { - strncpy(l->gc_password, password, 8); + l->gc_password = password; } if (temporary) { l->privileges |= Privilege::TEMPORARY; @@ -196,8 +186,8 @@ shared_ptr LicenseManager::create_license_bb( uint32_t serial_number, const char* username, const char* password, bool temporary) { shared_ptr l(new License()); l->serial_number = serial_number; - strncpy(l->username, username, 19); - strncpy(l->bb_password, password, 19); + l->username = username; + l->bb_password = password; if (temporary) { l->privileges |= Privilege::TEMPORARY; } diff --git a/src/License.hh b/src/License.hh index e3408aca..1d3aaf2d 100644 --- a/src/License.hh +++ b/src/License.hh @@ -5,6 +5,8 @@ #include #include +#include "Text.hh" + enum Privilege { KICK_USER = 0x00000001, BAN_USER = 0x00000002, @@ -30,11 +32,11 @@ enum LicenseVerifyAction { }; struct License { - char username[20]; // BB username (max. 16 chars; should technically be Unicode) - char bb_password[20]; // BB password (max. 16 chars) + ptext username; // BB username (max. 16 chars; should technically be Unicode) + ptext bb_password; // BB password (max. 16 chars) uint32_t serial_number; // PC/GC serial number. MUST BE PRESENT FOR BB LICENSES TOO; this is also the player's guild card number. - char access_key[16]; // PC/GC access key. (to log in using PC on a GC license, just enter the first 8 characters of the GC access key) - char gc_password[12]; // GC password + ptext access_key; // PC/GC access key. (to log in using PC on a GC license, just enter the first 8 characters of the GC access key) + ptext gc_password; // GC password uint32_t privileges; // privilege level uint64_t ban_end_time; // end time of ban (zero = not banned) diff --git a/src/Lobby.cc b/src/Lobby.cc index ba4fbefe..88c8372f 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -21,9 +21,6 @@ Lobby::Lobby() : lobby_id(0), min_level(0), max_level(0xFFFFFFFF), this->next_item_id[x] = 0; } memset(&this->next_drop_item, 0, sizeof(this->next_drop_item)); - memset(this->variations, 0, 0x20 * sizeof(this->variations[0])); - memset(this->password, 0, 36 * sizeof(this->password[0])); - memset(this->name, 0, 36 * sizeof(this->name[0])); } bool Lobby::is_game() const { @@ -152,7 +149,7 @@ shared_ptr Lobby::find_client(const char16_t* identifier, (this->clients[x]->license->serial_number == serial_number)) { return this->clients[x]; } - if (identifier && !char16ncmp(this->clients[x]->player.disp.name, identifier, 0x10)) { + if (identifier && (this->clients[x]->player.disp.name == identifier)) { return this->clients[x]; } } diff --git a/src/Lobby.hh b/src/Lobby.hh index 68baa7d2..3d8db9ad 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -33,7 +33,7 @@ struct Lobby { uint32_t next_game_item_id; PlayerInventoryItem next_drop_item; std::unordered_map item_id_to_floor_item; - uint32_t variations[0x20]; + parray variations; // game config GameVersion version; @@ -41,8 +41,8 @@ struct Lobby { uint8_t episode; uint8_t difficulty; uint8_t mode; - char16_t password[0x24]; - char16_t name[0x24]; + std::u16string password; + std::u16string name; uint32_t rare_seed; //EP3_GAME_CONFIG* ep3; // only present if this is an Episode 3 game diff --git a/src/Map.hh b/src/Map.hh index 4f162c11..fdc0a326 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -10,11 +10,11 @@ struct BattleParams { uint16_t atp; // attack power uint16_t psv; // perseverance (intelligence?) uint16_t evp; // evasion - uint16_t hp; // hit points + uint16_t hp; // hit points uint16_t dfp; // defense uint16_t ata; // accuracy uint16_t lck; // luck - uint8_t unknown[14]; + uint8_t unknown_a1[0x0E]; uint32_t experience; uint32_t difficulty; } __attribute__((packed)); diff --git a/src/Player.cc b/src/Player.cc index 137b9853..bb9af277 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -49,19 +49,15 @@ PlayerDispDataBB PlayerDispDataPCGC::to_bb() const { bb.stats.dfp = this->stats.dfp; bb.stats.ata = this->stats.ata; bb.stats.lck = this->stats.lck; - bb.unknown1 = this->unknown1; - bb.unknown2[0] = this->unknown2[0]; - bb.unknown2[1] = this->unknown2[1]; + bb.unknown_a1 = this->unknown_a1; bb.level = this->level; bb.experience = this->experience; bb.meseta = this->meseta; - memset(bb.guild_card, 0, sizeof(bb.guild_card)); - strncpy(bb.guild_card, " 0", 0x10); - bb.unknown3[0] = this->unknown3[0]; - bb.unknown3[1] = this->unknown3[1]; + bb.guild_card = " 0"; + bb.unknown_a2 = this->unknown_a2; bb.name_color = this->name_color; bb.extra_model = this->extra_model; - memcpy(&bb.unused, &this->unused, 15); + bb.unused = this->unused; bb.name_color_checksum = this->name_color_checksum; bb.section_id = this->section_id; bb.char_class = this->char_class; @@ -78,11 +74,9 @@ PlayerDispDataBB PlayerDispDataPCGC::to_bb() const { bb.hair_b = this->hair_b; bb.proportion_x = this->proportion_x; bb.proportion_y = this->proportion_y; - memset(bb.name, 0, sizeof(bb.name)); - decode_sjis(bb.name, this->name, 0x10); - add_language_marker_inplace(bb.name, 'J', 0x10); - memcpy(&bb.config, &this->config, 0x48); - memcpy(&bb.technique_levels, &this->technique_levels, 0x14); + bb.name = add_language_marker(this->name, 'J'); + bb.config = this->config; + bb.technique_levels = this->technique_levels; return bb; } @@ -96,17 +90,14 @@ PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const { pcgc.stats.dfp = this->stats.dfp; pcgc.stats.ata = this->stats.ata; pcgc.stats.lck = this->stats.lck; - pcgc.unknown1 = this->unknown1; - pcgc.unknown2[0] = this->unknown2[0]; - pcgc.unknown2[1] = this->unknown2[1]; + pcgc.unknown_a1 = this->unknown_a1; pcgc.level = this->level; pcgc.experience = this->experience; pcgc.meseta = this->meseta; - pcgc.unknown3[0] = this->unknown3[0]; - pcgc.unknown3[1] = this->unknown3[1]; + pcgc.unknown_a2 = this->unknown_a2; pcgc.name_color = this->name_color; pcgc.extra_model = this->extra_model; - memcpy(&pcgc.unused, &this->unused, 15); + pcgc.unused = this->unused; pcgc.name_color_checksum = this->name_color_checksum; pcgc.section_id = this->section_id; pcgc.char_class = this->char_class; @@ -123,11 +114,9 @@ PlayerDispDataPCGC PlayerDispDataBB::to_pcgc() const { pcgc.hair_b = this->hair_b; pcgc.proportion_x = this->proportion_x; pcgc.proportion_y = this->proportion_y; - memset(pcgc.name, 0, sizeof(pcgc.name)); - encode_sjis(pcgc.name, this->name, 0x10); - remove_language_marker_inplace(pcgc.name); - memcpy(&pcgc.config, &this->config, 0x48); - memcpy(&pcgc.technique_levels, &this->technique_levels, 0x14); + pcgc.name = remove_language_marker(this->name); + pcgc.config = this->config; + pcgc.technique_levels = this->technique_levels; return pcgc; } @@ -136,13 +125,11 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const { PlayerDispDataBBPreview pre; pre.level = this->level; pre.experience = this->experience; - memset(pre.guild_card, 0, sizeof(pre.guild_card)); - strncpy(pre.guild_card, this->guild_card, 0x10); - pre.unknown3[0] = this->unknown3[0]; - pre.unknown3[1] = this->unknown3[1]; + pre.guild_card = this->guild_card; + pre.unknown_a2 = this->unknown_a2; pre.name_color = this->name_color; pre.extra_model = this->extra_model; - memcpy(&pre.unused, &this->unused, 11); + pre.unused = this->unused; pre.name_color_checksum = this->name_color_checksum; pre.section_id = this->section_id; pre.char_class = this->char_class; @@ -159,22 +146,19 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const { pre.hair_b = this->hair_b; pre.proportion_x = this->proportion_x; pre.proportion_y = this->proportion_y; - memset(pre.name, 0, sizeof(pre.name)); - strcpy_z(pre.name, this->name, 16); - pre.play_time = this->play_time; + pre.name = this->name; + pre.play_time = 0; // TODO: Store this somewhere and return it here return pre; } void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) { this->level = pre.level; this->experience = pre.experience; - memset(this->guild_card, 0, sizeof(this->guild_card)); - strncpy(this->guild_card, pre.guild_card, 0x10); - this->unknown3[0] = pre.unknown3[0]; - this->unknown3[1] = pre.unknown3[1]; + this->guild_card = pre.guild_card; + this->unknown_a2 = pre.unknown_a2; this->name_color = pre.name_color; this->extra_model = pre.extra_model; - memcpy(&this->unused, &pre.unused, 11); + this->unused = pre.unused; this->name_color_checksum = pre.name_color_checksum; this->section_id = pre.section_id; this->char_class = pre.char_class; @@ -191,9 +175,7 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) { this->hair_b = pre.hair_b; this->proportion_x = pre.proportion_x; this->proportion_y = pre.proportion_y; - memset(this->name, 0, sizeof(this->name)); - strcpy_z(this->name, pre.name, 0x10); - this->play_time = 0; + this->name = pre.name; } @@ -214,43 +196,33 @@ void PlayerBank::save(const string& filename) const { void Player::import(const PSOPlayerDataPC& pc) { this->inventory = pc.inventory; this->disp = pc.disp.to_bb(); - /* TODO: fix and re-enable this functionality - memset(this->info_board, 0, sizeof(this->info_board)); - decode_sjis(this->info_board, pc->info_board); - memcpy(&this->blocked, pc->blocked, sizeof(uint32_t) * 30); - memset(this->auto_reply, 0, sizeof(this->auto_reply)); - if (pc->auto_reply_enabled) { - decode_sjis(this->auto_reply, pc->auto_reply); - } else {*/ - this->auto_reply[0] = 0; - //} + // TODO: Add these fields to the existing structure so we can parse them + // this->info_board = pc.info_board; + // this->blocked_senders = pc.blocked_senders; + // this->auto_reply = pc.auto_reply; } void Player::import(const PSOPlayerDataGC& gc) { this->inventory = gc.inventory; this->disp = gc.disp.to_bb(); - memset(this->info_board, 0, sizeof(this->info_board)); - decode_sjis(this->info_board, gc.info_board, 0xAC); - memcpy(&this->blocked, gc.blocked, sizeof(uint32_t) * 30); - memset(this->auto_reply, 0, sizeof(this->auto_reply)); + this->info_board = gc.info_board; + this->blocked_senders = gc.blocked_senders; if (gc.auto_reply_enabled) { - decode_sjis(this->auto_reply, gc.auto_reply, 0xAC); + this->auto_reply = gc.auto_reply; } else { - this->auto_reply[0] = 0; + this->auto_reply.clear(); } } void Player::import(const PSOPlayerDataBB& bb) { - // note: we don't copy the inventory and disp here because we already have + // Note: we don't copy the inventory and disp here because we already have // it (we sent the player data to the client in the first place) - memset(this->info_board, 0, sizeof(this->info_board)); - strcpy_z(this->info_board, bb.info_board, 0xAC); - memcpy(&this->blocked, bb.blocked, sizeof(uint32_t) * 30); - memset(this->auto_reply, 0, sizeof(this->auto_reply)); + this->info_board = bb.info_board; + this->blocked_senders = bb.blocked_senders; if (bb.auto_reply_enabled) { - strcpy_z(this->auto_reply, bb.auto_reply, 0xAC); + this->auto_reply = bb.auto_reply; } else { - this->auto_reply[0] = 0; + this->auto_reply.clear(); } } @@ -258,33 +230,28 @@ PlayerBB Player::export_bb_player_data() const { PlayerBB bb; bb.inventory = this->inventory; bb.disp = this->disp; - memset(bb.unknown, 0, 0x10); + bb.unknown.clear(); bb.option_flags = this->option_flags; - memcpy(bb.quest_data1, &this->quest_data1, 0x0208); + bb.quest_data1 = this->quest_data1; bb.bank = this->bank; bb.serial_number = this->serial_number; - memset(bb.name, 0, sizeof(bb.name)); - strcpy_z(bb.name, this->disp.name, 24); - memset(bb.team_name, 0, sizeof(bb.team_name)); - strcpy_z(bb.team_name, this->team_name, 16); - memset(bb.guild_card_desc, 0, sizeof(bb.guild_card_desc)); - strcpy_z(bb.guild_card_desc, this->guild_card_desc, 0x58); + bb.name = this->disp.name; + bb.team_name = this->team_name; + bb.guild_card_desc = this->guild_card_desc; bb.reserved1 = 0; bb.reserved2 = 0; bb.section_id = this->disp.section_id; bb.char_class = this->disp.char_class; bb.unknown3 = 0; - memcpy(bb.symbol_chats, this->symbol_chats, 0x04E0); - memcpy(bb.shortcuts, this->shortcuts, 0x0A40); - memset(bb.auto_reply, 0, sizeof(bb.auto_reply)); - strcpy_z(bb.auto_reply, this->auto_reply, 0xAC); - memset(bb.info_board, 0, sizeof(bb.info_board)); - strcpy_z(bb.info_board, this->info_board, 0xAC); - memset(bb.unknown5, 0, 0x1C); - memcpy(bb.challenge_data, this->challenge_data, 0x0140); - memcpy(bb.tech_menu_config, this->tech_menu_config, 0x0028); - memset(bb.unknown6, 0, 0x2C); - memcpy(bb.quest_data2, &this->quest_data2, 0x0058); + bb.symbol_chats = this->symbol_chats; + bb.shortcuts = this->shortcuts; + bb.auto_reply = this->auto_reply; + bb.info_board = this->info_board; + bb.unknown5.clear(); + bb.challenge_data = this->challenge_data; + bb.tech_menu_config = this->tech_menu_config; + bb.unknown6.clear(); + bb.quest_data2 = this->quest_data2; bb.key_config = this->key_config; return bb; } @@ -311,78 +278,62 @@ uint32_t compute_guild_card_checksum(const void* vdata, size_t size) { void Player::load_account_data(const string& filename) { SavedAccountBB account = load_object_file(filename); - - if (strcmp(account.signature, ACCOUNT_FILE_SIGNATURE)) { + if (account.signature != ACCOUNT_FILE_SIGNATURE) { throw runtime_error("account data header is incorrect"); } - - memcpy(&this->blocked, &account.blocked, sizeof(uint32_t) * 30); + this->blocked_senders = account.blocked_senders; this->guild_cards = account.guild_cards; this->key_config = account.key_config; this->option_flags = account.option_flags; - memcpy(&this->shortcuts, &account.shortcuts, 0x0A40); - memcpy(&this->symbol_chats, &account.symbol_chats, 0x04E0); - memset(this->team_name, 0, sizeof(this->team_name)); - strcpy_z(this->team_name, account.team_name, 16); + this->shortcuts = account.shortcuts; + this->symbol_chats = account.symbol_chats; + this->team_name = account.team_name; } void Player::save_account_data(const string& filename) const { SavedAccountBB account; - - strncpy(account.signature, ACCOUNT_FILE_SIGNATURE, sizeof(account.signature)); - memcpy(&account.blocked, &this->blocked, sizeof(uint32_t) * 30); + account.signature = ACCOUNT_FILE_SIGNATURE; + account.blocked_senders = this->blocked_senders; account.guild_cards = this->guild_cards; account.key_config = this->key_config; account.option_flags = this->option_flags; - memcpy(&account.shortcuts, &this->shortcuts, 0x0A40); - memcpy(&account.symbol_chats, &this->symbol_chats, 0x04E0); - memset(account.team_name, 0, sizeof(account.team_name)); - strcpy_z(account.team_name, this->team_name, 16); - + account.shortcuts = this->shortcuts; + account.symbol_chats = this->symbol_chats; + account.team_name = this->team_name; save_file(filename, &account, sizeof(account)); } void Player::load_player_data(const string& filename) { SavedPlayerBB player = load_object_file(filename); - - if (strcmp(player.signature, PLAYER_FILE_SIGNATURE)) { + if (player.signature != PLAYER_FILE_SIGNATURE) { throw runtime_error("account data header is incorrect"); } - - memset(this->auto_reply, 0, sizeof(this->auto_reply)); - strcpy_z(this->auto_reply, player.auto_reply, 0xAC); + this->auto_reply = player.auto_reply; this->bank = player.bank; - memcpy(&this->challenge_data, &player.challenge_data, 0x0140); + this->challenge_data = player.challenge_data; this->disp = player.disp; - memset(this->guild_card_desc, 0, sizeof(this->guild_card_desc)); - strcpy_z(this->guild_card_desc, player.guild_card_desc, 0x58); - memset(this->info_board, 0, sizeof(this->info_board)); - strcpy_z(this->info_board, player.info_board, 0xAC); + this->guild_card_desc = player.guild_card_desc; + this->info_board = player.info_board; this->inventory = player.inventory; - memcpy(&this->quest_data1, &player.quest_data1, 0x0208); - memcpy(&this->quest_data2, &player.quest_data2, 0x0058); - memcpy(&this->tech_menu_config, &player.tech_menu_config, 0x0028); + this->quest_data1 = player.quest_data1; + this->quest_data2 = player.quest_data2; + this->tech_menu_config = player.tech_menu_config; } void Player::save_player_data(const string& filename) const { SavedPlayerBB player; - - strncpy(player.signature, PLAYER_FILE_SIGNATURE, sizeof(player.signature)); + player.signature = PLAYER_FILE_SIGNATURE; player.preview = this->disp.to_preview(); - memset(player.auto_reply, 0, sizeof(player.auto_reply)); - strcpy_z(player.auto_reply, this->auto_reply, 0xAC); + player.auto_reply = this->auto_reply; player.bank = this->bank; - memcpy(&player.challenge_data, &this->challenge_data, 0x0140); + player.challenge_data = this->challenge_data; player.disp = this->disp; - memset(player.guild_card_desc, 0, sizeof(player.guild_card_desc)); - strcpy_z(player.guild_card_desc,this->guild_card_desc, 0x58); - memset(player.info_board, 0, sizeof(player.info_board)); - strcpy_z(player.info_board, this->info_board, 0xAC); + player.guild_card_desc = this->guild_card_desc; + player.info_board = this->info_board; player.inventory = this->inventory; - memcpy(&player.quest_data1, &this->quest_data1, 0x0208); - memcpy(&player.quest_data2, &this->quest_data2, 0x0058); - memcpy(&player.tech_menu_config, &this->tech_menu_config, 0x0028); - + player.quest_data1 = this->quest_data1; + player.quest_data2 = this->quest_data2; + player.tech_menu_config = this->tech_menu_config; save_file(filename, &player, sizeof(player)); } @@ -639,9 +590,9 @@ string filename_for_player_bb(const string& username, uint8_t player_index) { static_cast(player_index + 1)); } -string filename_for_bank_bb(const string& username, const char* bank_name) { +string filename_for_bank_bb(const string& username, const std::string& bank_name) { return string_printf("system/players/bank_%s_%s.nsb", username.c_str(), - bank_name); + bank_name.c_str()); } string filename_for_class_template_bb(uint8_t char_class) { diff --git a/src/Player.hh b/src/Player.hh index 151c1759..55503b5c 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -8,21 +8,23 @@ #include #include "Version.hh" +#include "Text.hh" // raw item data +// TODO: use parray for the fields here struct ItemData { union { uint8_t item_data1[12]; - uint16_t item_data1w[6]; - uint32_t item_data1d[3]; + le_uint16_t item_data1w[6]; + le_uint32_t item_data1d[3]; } __attribute__((packed)); uint32_t item_id; union { uint8_t item_data2[4]; - uint16_t item_data2w[2]; - uint32_t item_data2d; + le_uint16_t item_data2w[2]; + le_uint32_t item_data2d; } __attribute__((packed)); uint32_t primary_identifier() const; @@ -32,9 +34,9 @@ struct PlayerBankItem; // an item in a player's inventory struct PlayerInventoryItem { - uint16_t equip_flags; - uint16_t tech_flag; - uint32_t game_flags; + le_uint16_t equip_flags; + le_uint16_t tech_flag; + le_uint32_t game_flags; ItemData data; PlayerBankItem to_bank_item() const; @@ -43,8 +45,8 @@ struct PlayerInventoryItem { // an item in a player's bank struct PlayerBankItem { ItemData data; - uint16_t amount; - uint16_t show_flags; + le_uint16_t amount; + le_uint16_t show_flags; PlayerInventoryItem to_inventory_item() const; } __attribute__((packed)); @@ -62,8 +64,8 @@ struct PlayerInventory { // a player's bank struct PlayerBank { - uint32_t num_items; - uint32_t meseta; + le_uint32_t num_items; + le_uint32_t meseta; PlayerBankItem items[200]; void load(const std::string& filename); @@ -80,13 +82,13 @@ struct PlayerBank { // simple player stats struct PlayerStats { - uint16_t atp; - uint16_t mst; - uint16_t evp; - uint16_t hp; - uint16_t dfp; - uint16_t ata; - uint16_t lck; + le_uint16_t atp; + le_uint16_t mst; + le_uint16_t evp; + le_uint16_t hp; + le_uint16_t dfp; + le_uint16_t ata; + le_uint16_t lck; } __attribute__((packed)); struct PlayerDispDataBB; @@ -94,34 +96,33 @@ struct PlayerDispDataBB; // PC/GC player appearance and stats data struct PlayerDispDataPCGC { // 0xD0 in size PlayerStats stats; - uint16_t unknown1; - uint32_t unknown2[2]; - uint32_t level; - uint32_t experience; - uint32_t meseta; - char name[16]; - uint32_t unknown3[2]; - uint32_t name_color; + parray unknown_a1; + le_uint32_t level; + le_uint32_t experience; + le_uint32_t meseta; + ptext name; + uint64_t unknown_a2; + le_uint32_t name_color; uint8_t extra_model; - uint8_t unused[15]; - uint32_t name_color_checksum; + parray unused; + le_uint32_t name_color_checksum; uint8_t section_id; uint8_t char_class; uint8_t v2_flags; uint8_t version; - uint32_t v1_flags; - uint16_t costume; - uint16_t skin; - uint16_t face; - uint16_t head; - uint16_t hair; - uint16_t hair_r; - uint16_t hair_g; - uint16_t hair_b; - float proportion_x; - float proportion_y; - uint8_t config[0x48]; - uint8_t technique_levels[0x14]; + le_uint32_t v1_flags; + le_uint16_t costume; + le_uint16_t skin; + le_uint16_t face; + le_uint16_t head; + le_uint16_t hair; + le_uint16_t hair_r; + le_uint16_t hair_g; + le_uint16_t hair_b; + le_float proportion_x; + le_float proportion_y; + parray config; + parray technique_levels; void enforce_pc_limits(); PlayerDispDataBB to_bb() const; @@ -129,66 +130,64 @@ struct PlayerDispDataPCGC { // 0xD0 in size // BB player preview format struct PlayerDispDataBBPreview { - uint32_t experience; - uint32_t level; - char guild_card[16]; - uint32_t unknown3[2]; - uint32_t name_color; + le_uint32_t experience; + le_uint32_t level; + ptext guild_card; + uint64_t unknown_a2; + le_uint32_t name_color; uint8_t extra_model; - uint8_t unused[15]; - uint32_t name_color_checksum; + parray unused; + le_uint32_t name_color_checksum; uint8_t section_id; uint8_t char_class; uint8_t v2_flags; uint8_t version; - uint32_t v1_flags; - uint16_t costume; - uint16_t skin; - uint16_t face; - uint16_t head; - uint16_t hair; - uint16_t hair_r; - uint16_t hair_g; - uint16_t hair_b; - float proportion_x; - float proportion_y; - char16_t name[16]; + le_uint32_t v1_flags; + le_uint16_t costume; + le_uint16_t skin; + le_uint16_t face; + le_uint16_t head; + le_uint16_t hair; + le_uint16_t hair_r; + le_uint16_t hair_g; + le_uint16_t hair_b; + le_float proportion_x; + le_float proportion_y; + ptext name; uint32_t play_time; } __attribute__((packed)); // BB player appearance and stats data struct PlayerDispDataBB { PlayerStats stats; - uint16_t unknown1; - uint32_t unknown2[2]; - uint32_t level; - uint32_t experience; - uint32_t meseta; - char guild_card[0x10]; - uint32_t unknown3[2]; - uint32_t name_color; + parray unknown_a1; + le_uint32_t level; + le_uint32_t experience; + le_uint32_t meseta; + ptext guild_card; + uint64_t unknown_a2; + le_uint32_t name_color; uint8_t extra_model; - uint8_t unused[11]; - uint32_t play_time; // not actually a game field; used only by my server - uint32_t name_color_checksum; + parray unused; + le_uint32_t name_color_checksum; uint8_t section_id; uint8_t char_class; uint8_t v2_flags; uint8_t version; - uint32_t v1_flags; - uint16_t costume; - uint16_t skin; - uint16_t face; - uint16_t head; - uint16_t hair; - uint16_t hair_r; - uint16_t hair_g; - uint16_t hair_b; - float proportion_x; - float proportion_y; - char16_t name[0x10]; - uint8_t config[0xE8]; - uint8_t technique_levels[0x14]; + le_uint32_t v1_flags; + le_uint16_t costume; + le_uint16_t skin; + le_uint16_t face; + le_uint16_t head; + le_uint16_t hair; + le_uint16_t hair_r; + le_uint16_t hair_g; + le_uint16_t hair_b; + le_float proportion_x; + le_float proportion_y; + ptext name; + parray config; + parray technique_levels; inline void enforce_pc_limits() { } PlayerDispDataPCGC to_pcgc() const; @@ -199,10 +198,10 @@ struct PlayerDispDataBB { struct GuildCardGC { - uint32_t player_tag; - uint32_t serial_number; - char name[0x18]; - char desc[0x6C]; + le_uint32_t player_tag; + le_uint32_t serial_number; + ptext name; + ptext desc; uint8_t reserved1; // should be 1 uint8_t reserved2; // should be 1 uint8_t section_id; @@ -211,10 +210,10 @@ struct GuildCardGC { // BB guild card format struct GuildCardBB { - uint32_t serial_number; - char16_t name[0x18]; - char16_t teamname[0x10]; - char16_t desc[0x58]; + le_uint32_t serial_number; + ptext name; + ptext teamname; + ptext desc; uint8_t reserved1; // should be 1 uint8_t reserved2; // should be 1 uint8_t section_id; @@ -224,66 +223,66 @@ struct GuildCardBB { // an entry in the BB guild card file struct GuildCardEntryBB { GuildCardBB data; - uint8_t unknown[0xB4]; + parray unknown; } __attribute__((packed)); // the format of the BB guild card file struct GuildCardFileBB { - uint8_t unknown[0x1F84]; + parray unknown_a1; GuildCardEntryBB entry[0x0068]; // that's 104 of them in decimal - uint8_t unknown2[0x01AC]; + parray unknown_a2; } __attribute__((packed)); // PSOBB key config and team info struct KeyAndTeamConfigBB { - uint8_t unknown[0x0114]; // 0000 - uint8_t key_config[0x016C]; // 0114 - uint8_t joystick_config[0x0038]; // 0280 - uint32_t serial_number; // 02B8 - uint32_t team_id; // 02BC - uint32_t team_info[2]; // 02C0 - uint16_t team_privilege_level; // 02C8 - uint16_t reserved; // 02CA - char16_t team_name[0x0010]; // 02CC - uint8_t team_flag[0x0800]; // 02EC - uint32_t team_rewards[2]; // 0AEC + parray unknown_a1; // 0000 + parray key_config; // 0114 + parray joystick_config; // 0280 + le_uint32_t serial_number; // 02B8 + le_uint32_t team_id; // 02BC + le_uint64_t team_info; // 02C0 + le_uint16_t team_privilege_level; // 02C8 + le_uint16_t reserved; // 02CA + ptext team_name; // 02CC + parray team_flag; // 02EC + le_uint64_t team_rewards; // 0AEC } __attribute__((packed)); // BB account data struct PlayerAccountDataBB { - uint8_t symbol_chats[0x04E0]; + parray symbol_chats; KeyAndTeamConfigBB key_config; GuildCardFileBB guild_cards; - uint32_t options; - uint8_t shortcuts[0x0A40]; // chat shortcuts (@1FB4 in E7 command) + le_uint32_t options; + parray shortcuts; // chat shortcuts (@1FB4 in E7 command) } __attribute__((packed)); struct PlayerLobbyDataPC { - uint32_t player_tag; - uint32_t guild_card; + le_uint32_t player_tag; + le_uint32_t guild_card; be_uint32_t ip_address; - uint32_t client_id; - char16_t name[16]; + le_uint32_t client_id; + ptext name; } __attribute__((packed)); struct PlayerLobbyDataGC { - uint32_t player_tag; - uint32_t guild_card; + le_uint32_t player_tag; + le_uint32_t guild_card; be_uint32_t ip_address; - uint32_t client_id; - char name[16]; + le_uint32_t client_id; + ptext name; } __attribute__((packed)); struct PlayerLobbyDataBB { - uint32_t player_tag; - uint32_t guild_card; + le_uint32_t player_tag; + le_uint32_t guild_card; be_uint32_t ip_address; // Guess - the official builds didn't use this, but all other versions have it - uint32_t unknown1[4]; - uint32_t client_id; - char16_t name[16]; - uint32_t unknown2; + parray unknown_a1; + le_uint32_t client_id; + ptext name; + le_uint32_t unknown2; } __attribute__((packed)); @@ -296,107 +295,105 @@ struct PSOPlayerDataPC { // for command 0x61 struct PSOPlayerDataGC { // for command 0x61 PlayerInventory inventory; PlayerDispDataPCGC disp; - char unknown[0x134]; - char info_board[0xAC]; - uint32_t blocked[0x1E]; - uint32_t auto_reply_enabled; + parray unknown; + ptext info_board; + parray blocked_senders; + le_uint32_t auto_reply_enabled; char auto_reply[0]; } __attribute__((packed)); struct PSOPlayerDataBB { // for command 0x61 PlayerInventory inventory; PlayerDispDataBB disp; - char unused[0x174]; - char16_t info_board[0xAC]; - uint32_t blocked[0x1E]; - uint32_t auto_reply_enabled; + ptext unused; + ptext info_board; + parray blocked_senders; + le_uint32_t auto_reply_enabled; char16_t auto_reply[0]; } __attribute__((packed)); // complete BB player data format (used in E7 command) struct PlayerBB { - PlayerInventory inventory; // 0000 // player - PlayerDispDataBB disp; // 034C // player - uint8_t unknown[0x0010]; // 04DC // - uint32_t option_flags; // 04EC // account - uint8_t quest_data1[0x0208]; // 04F0 // player - PlayerBank bank; // 06F8 // player - uint32_t serial_number; // 19C0 // player - char16_t name[0x18]; // 19C4 // player - char16_t team_name[0x10]; // 19C4 // player - char16_t guild_card_desc[0x58]; // 1A14 // player - uint8_t reserved1; // 1AC4 // player - uint8_t reserved2; // 1AC5 // player - uint8_t section_id; // 1AC6 // player - uint8_t char_class; // 1AC7 // player - uint32_t unknown3; // 1AC8 // - uint8_t symbol_chats[0x04E0]; // 1ACC // account - uint8_t shortcuts[0x0A40]; // 1FAC // account - char16_t auto_reply[0x00AC]; // 29EC // player - char16_t info_board[0x00AC]; // 2B44 // player - uint8_t unknown5[0x001C]; // 2C9C // - uint8_t challenge_data[0x0140]; // 2CB8 // player - uint8_t tech_menu_config[0x0028]; // 2DF8 // player - uint8_t unknown6[0x002C]; // 2E20 // - uint8_t quest_data2[0x0058]; // 2E4C // player - KeyAndTeamConfigBB key_config; // 2EA4 // account -} __attribute__((packed)); // total size: 39A0 + PlayerInventory inventory; // 0000 // player + PlayerDispDataBB disp; // 034C // player + parray unknown; // 04DC // + le_uint32_t option_flags; // 04EC // account + parray quest_data1; // 04F0 // player + PlayerBank bank; // 06F8 // player + le_uint32_t serial_number; // 19C0 // player + ptext name; // 19C4 // player + ptext team_name; // 19C4 // player + ptext guild_card_desc; // 1A14 // player + uint8_t reserved1; // 1AC4 // player + uint8_t reserved2; // 1AC5 // player + uint8_t section_id; // 1AC6 // player + uint8_t char_class; // 1AC7 // player + le_uint32_t unknown3; // 1AC8 // + parray symbol_chats; // 1ACC // account + parray shortcuts; // 1FAC // account + ptext auto_reply; // 29EC // player + ptext info_board; // 2B44 // player + parray unknown5; // 2C9C // + parray challenge_data; // 2CB8 // player + parray tech_menu_config; // 2DF8 // player + parray unknown6; // 2E20 // + parray quest_data2; // 2E4C // player + KeyAndTeamConfigBB key_config; // 2EA4 // account +} __attribute__((packed)); // total size: 39A0 struct SavedPlayerBB { // .nsc file format - char signature[0x40]; + ptext signature; PlayerDispDataBBPreview preview; - - char16_t auto_reply[0x00AC]; - PlayerBank bank; - uint8_t challenge_data[0x0140]; - PlayerDispDataBB disp; - char16_t guild_card_desc[0x58]; - char16_t info_board[0x00AC]; - PlayerInventory inventory; - uint8_t quest_data1[0x0208]; - uint8_t quest_data2[0x0058]; - uint8_t tech_menu_config[0x0028]; + ptext auto_reply; + PlayerBank bank; + parray challenge_data; + PlayerDispDataBB disp; + ptext guild_card_desc; + ptext info_board; + PlayerInventory inventory; + parray quest_data1; + parray quest_data2; + parray tech_menu_config; } __attribute__((packed)); struct SavedAccountBB { // .nsa file format - char signature[0x40]; - uint32_t blocked[0x001E]; - GuildCardFileBB guild_cards; - KeyAndTeamConfigBB key_config; - uint32_t option_flags; - uint8_t shortcuts[0x0A40]; - uint8_t symbol_chats[0x04E0]; - char16_t team_name[0x0010]; + ptext signature; + parray blocked_senders; + GuildCardFileBB guild_cards; + KeyAndTeamConfigBB key_config; + le_uint32_t option_flags; + parray shortcuts; + parray symbol_chats; + ptext team_name; } __attribute__((packed)); // complete player info stored by the server struct Player { - uint32_t loaded_from_shipgate_time; - - char16_t auto_reply[0x00AC]; // player - PlayerBank bank; // player - char bank_name[0x20]; - uint32_t blocked[0x001E]; // account - uint8_t challenge_data[0x0140]; // player - PlayerDispDataBB disp; // player - uint8_t ep3_config[0x2408]; - char16_t guild_card_desc[0x58]; // player - GuildCardFileBB guild_cards; // account - PlayerInventoryItem identify_result; - char16_t info_board[0x00AC]; // player - PlayerInventory inventory; // player - KeyAndTeamConfigBB key_config; // account - uint32_t option_flags; // account - uint8_t quest_data1[0x0208]; // player - uint8_t quest_data2[0x0058]; // player - uint32_t serial_number; - std::vector current_shop_contents; - uint8_t shortcuts[0x0A40]; // account - uint8_t symbol_chats[0x04E0]; // account - char16_t team_name[0x0010]; // account - uint8_t tech_menu_config[0x0028]; // player + le_uint32_t loaded_from_shipgate_time; + ptext auto_reply; // player + PlayerBank bank; // player + ptext bank_name; // not saved + parray blocked_senders; // account + parray challenge_data; // player + PlayerDispDataBB disp; // player + parray ep3_config; // not saved + ptext guild_card_desc; // player + GuildCardFileBB guild_cards; // account + PlayerInventoryItem identify_result; // not saved + ptext info_board; // player + PlayerInventory inventory; // player + KeyAndTeamConfigBB key_config; // account + le_uint32_t option_flags; // account + parray quest_data1; // player + parray quest_data2; // player + le_uint32_t serial_number; // account identifier + std::vector current_shop_contents; // not saved + parray shortcuts; // account + parray symbol_chats; // account + ptext team_name; // account + parray tech_menu_config; // player void load_player_data(const std::string& filename); void save_player_data(const std::string& filename) const; @@ -419,7 +416,7 @@ struct Player { uint32_t compute_guild_card_checksum(const void* data, size_t size); std::string filename_for_player_bb(const std::string& username, uint8_t player_index); -std::string filename_for_bank_bb(const std::string& username, const char* bank_name); +std::string filename_for_bank_bb(const std::string& username, const std::string& bank_name); std::string filename_for_class_template_bb(uint8_t char_class); std::string filename_for_account_bb(const std::string& username); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index f56b5685..8d432810 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -198,14 +198,14 @@ void ProxyServer::UnlinkedSession::on_client_input() { should_close_unlinked_session = true; } else { const auto* cmd = reinterpret_cast(data.data()); - uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16); + uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16); try { license = this->server->state->license_manager->verify_gc( - serial_number, cmd->access_key, nullptr); + serial_number, cmd->access_key.c_str(), nullptr); sub_version = cmd->sub_version; character_name = cmd->name; memcpy(&client_config, &cmd->cfg, offsetof(ClientConfig, unused_bb_only)); - memset(client_config.unused_bb_only, 0xFF, sizeof(client_config.unused_bb_only)); + client_config.unused_bb_only.clear(0xFF); } catch (const exception& e) { log(ERROR, "[ProxyServer] Unlinked client has no valid license"); should_close_unlinked_session = true; @@ -306,7 +306,6 @@ ProxyServer::LinkedSession::LinkedSession( enable_chat_filter(true), lobby_players(12), lobby_client_id(0) { - memset(this->remote_client_config_data, 0, 0x20); memset(&this->next_destination, 0, sizeof(this->next_destination)); struct sockaddr_in* dest_sin = reinterpret_cast(&this->next_destination); dest_sin->sin_family = AF_INET; @@ -625,14 +624,13 @@ void ProxyServer::LinkedSession::on_server_input() { if (command == 0x17) { C_VerifyLicense_GC_DB cmd; memset(&cmd, 0, sizeof(cmd)); - snprintf(cmd.serial_number, sizeof(cmd.serial_number), "%08" PRIX32 "", + cmd.serial_number = string_printf("%08" PRIX32 "", this->license->serial_number); - memcpy(cmd.access_key, this->license->access_key, 0x10); + cmd.access_key = this->license->access_key; cmd.sub_version = this->sub_version; - snprintf(cmd.serial_number2, sizeof(cmd.serial_number2), "%08" PRIX32 "", - this->license->serial_number); - memcpy(cmd.access_key2, this->license->access_key, 0x10); - memcpy(cmd.password, this->license->gc_password, 0x0C); + cmd.serial_number2 = cmd.serial_number; + cmd.access_key2 = cmd.access_key; + cmd.password = this->license->gc_password; send_command(this->server_bev.get(), this->version, this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd), name.c_str()); @@ -656,15 +654,14 @@ void ProxyServer::LinkedSession::on_server_input() { cmd.guild_card_number = this->guild_card_number; } cmd.sub_version = this->sub_version; - cmd.unused2[1] = 1; - snprintf(cmd.serial_number, sizeof(cmd.serial_number), "%08" PRIX32 "", + cmd.unused2.data()[1] = 1; + cmd.serial_number = string_printf("%08" PRIX32 "", this->license->serial_number); - memcpy(cmd.access_key, this->license->access_key, 0x10); - snprintf(cmd.serial_number2, sizeof(cmd.serial_number2), "%08" PRIX32 "", - this->license->serial_number); - memcpy(cmd.access_key2, this->license->access_key, 0x10); - strncpy(cmd.name, this->character_name.c_str(), sizeof(cmd.name) - 1); - memcpy(&cmd.cfg, this->remote_client_config_data, 0x20); + cmd.access_key = this->license->access_key; + cmd.serial_number2 = cmd.serial_number; + cmd.access_key2 = cmd.access_key; + cmd.name = this->character_name; + memcpy(&cmd.cfg, this->remote_client_config_data.data(), 0x20); // If there's a guild card number, a shorter 9E is sent that ends // right after the client config data @@ -701,14 +698,14 @@ void ProxyServer::LinkedSession::on_server_input() { // server init command). We simulate that bug here. // If there was previously a guild card number, assume we got the // lobby server init text instead of the port map init text. - memcpy( - this->remote_client_config_data, + memcpy(this->remote_client_config_data.data(), had_guild_card_number ? "t Lobby Server. Copyright SEGA E" : "t Port Map. Copyright SEGA Enter", - 0x20); - memcpy(this->remote_client_config_data, &cmd->cfg, - min(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04), 0x20)); + this->remote_client_config_data.bytes()); + memcpy(this->remote_client_config_data.data(), &cmd->cfg, + min(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04), + this->remote_client_config_data.bytes())); // If the guild card number was not set, pretend (to the server) // that this is the first 04 command the client has received. The @@ -798,7 +795,8 @@ void ProxyServer::LinkedSession::on_server_input() { const auto* cmd = reinterpret_cast(data.data()); string output_filename = string_printf("%s.%s.%" PRIu64, - cmd->filename, is_download_quest ? "download" : "online", now()); + cmd->filename.c_str(), + is_download_quest ? "download" : "online", now()); for (size_t x = 0; x < output_filename.size(); x++) { if (output_filename[x] < 0x20 || output_filename[x] > 0x7E || output_filename[x] == '/') { output_filename[x] = '_'; @@ -833,7 +831,7 @@ void ProxyServer::LinkedSession::on_server_input() { sf = &this->saving_files.at(cmd->filename); } catch (const out_of_range&) { log(WARNING, "[ProxyServer/%08" PRIX32 "] Received data for non-open file %s", - this->license->serial_number, cmd->filename); + this->license->serial_number, cmd->filename.c_str()); break; } diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 9664cc96..e61bbe11 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -46,7 +46,7 @@ public: std::string character_name; uint32_t guild_card_number; - uint8_t remote_client_config_data[0x20]; + parray remote_client_config_data; ClientConfig newserv_client_config; bool suppress_newserv_commands; bool enable_chat_filter; diff --git a/src/Quest.cc b/src/Quest.cc index 7708aeda..f7b1c13b 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -77,9 +77,9 @@ struct PSOQuestHeaderDC { // same for dc v1 and v2, thankfully uint8_t is_download; uint8_t unknown1; uint16_t quest_number; // 0xFFFF for challenge quests - char name[0x20]; - char short_description[0x80]; - char long_description[0x120]; + ptext name; + ptext short_description; + ptext long_description; } __attribute__((packed)); struct PSOQuestHeaderPC { @@ -90,9 +90,9 @@ struct PSOQuestHeaderPC { uint8_t is_download; uint8_t unknown1; uint16_t quest_number; // 0xFFFF for challenge quests - char16_t name[0x20]; - char16_t short_description[0x80]; - char16_t long_description[0x120]; + ptext name; + ptext short_description; + ptext long_description; } __attribute__((packed)); struct PSOQuestHeaderGC { @@ -104,21 +104,21 @@ struct PSOQuestHeaderGC { uint8_t unknown1; uint8_t quest_number; uint8_t episode; // 1 = ep2. apparently some quests have 0xFF here, which means ep1 (?) - char name[0x20]; - char short_description[0x80]; - char long_description[0x120]; + ptext name; + ptext short_description; + ptext long_description; } __attribute__((packed)); struct PSOQuestHeaderGCEpisode3 { // there's actually a lot of other important stuff in here but I'm lazy. it // looks like map data, cutscene data, and maybe special cards used during // the quest - uint8_t unused[0x1DF0]; - char name[0x14]; - char location[0x14]; - char location2[0x3C]; - char description[0x190]; - uint8_t unused2[0x3A34]; + parray unknown_a1; + ptext name; + ptext location; + ptext location2; + ptext description; + parray unknown_a2; } __attribute__((packed)); struct PSOQuestHeaderBB { @@ -132,9 +132,9 @@ struct PSOQuestHeaderBB { uint8_t max_players; uint8_t joinable_in_progress; uint8_t unknown; - char16_t name[0x20]; - char16_t short_description[0x80]; - char16_t long_description[0x120]; + ptext name; + ptext short_description; + ptext long_description; } __attribute__((packed)); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 781f4cb5..c71d24f5 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -159,7 +159,7 @@ void process_login_complete(shared_ptr s, shared_ptr c) { c->player.load_account_data("system/blueburst/default.nsa"); } - sprintf(c->player.bank_name, "player%d", c->bb_player_index + 1); + c->player.bank_name = string_printf("player%d", c->bb_player_index + 1); string player_filename = filename_for_player_bb(c->license->username, c->bb_player_index); @@ -198,7 +198,10 @@ void process_disconnect(shared_ptr s, shared_ptr c) { } if (c->version == GameVersion::BB) { - c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000); + // TODO: Make a timer event for each connected player that saves their data + // periodically, not only when they disconnect + // TODO: Track play time somewhere + // c->player.disp.play_time += ((now() - c->play_time_begin) / 1000000); string account_filename = filename_for_account_bb(c->license->username); string player_filename = filename_for_player_bb(c->license->username, c->bb_player_index); @@ -218,10 +221,10 @@ void process_verify_license_gc(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // DB const auto& cmd = check_size_t(data); - uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16); try { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key, - cmd.password); + c->license = s->license_manager->verify_gc(serial_number, + cmd.access_key.c_str(), cmd.password.c_str()); } catch (const exception& e) { if (!s->allow_unregistered_users) { u16string message = u"Login failed: " + decode_sjis(e.what()); @@ -230,7 +233,7 @@ void process_verify_license_gc(shared_ptr s, shared_ptr c, return; } else { auto l = LicenseManager::create_license_gc(serial_number, - cmd.access_key, cmd.password, true); + cmd.access_key.c_str(), cmd.password.c_str(), true); s->license_manager->add(l); c->license = l; } @@ -244,14 +247,14 @@ void process_login_a_dc_pc_gc(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // 9A const auto& cmd = check_size_t(data); - uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16); try { if (c->version == GameVersion::GC) { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key, - nullptr); + c->license = s->license_manager->verify_gc(serial_number, + cmd.access_key.c_str(), nullptr); } else { - c->license = s->license_manager->verify_pc(serial_number, cmd.access_key, - nullptr); + c->license = s->license_manager->verify_pc(serial_number, + cmd.access_key.c_str(), nullptr); } } catch (const exception& e) { // The client should have sent a different command containing the password @@ -273,14 +276,14 @@ void process_login_c_dc_pc_gc(shared_ptr s, shared_ptr c, c->flags |= flags_for_version(c->version, cmd.sub_version); - uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16); try { if (c->version == GameVersion::GC) { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key, - cmd.password); + c->license = s->license_manager->verify_gc(serial_number, + cmd.access_key.c_str(), cmd.password.c_str()); } else { - c->license = s->license_manager->verify_pc(serial_number, cmd.access_key, - cmd.password); + c->license = s->license_manager->verify_pc(serial_number, + cmd.access_key.c_str(), cmd.password.c_str()); } } catch (const exception& e) { if (!s->allow_unregistered_users) { @@ -292,10 +295,10 @@ void process_login_c_dc_pc_gc(shared_ptr s, shared_ptr c, shared_ptr l; if (c->version == GameVersion::GC) { l = LicenseManager::create_license_gc(serial_number, - cmd.access_key, cmd.password, true); + cmd.access_key.c_str(), cmd.password.c_str(), true); } else { l = LicenseManager::create_license_pc(serial_number, - cmd.access_key, cmd.password, true); + cmd.access_key.c_str(), cmd.password.c_str(), true); } s->license_manager->add(l); c->license = l; @@ -313,14 +316,14 @@ void process_login_d_e_pc_gc(shared_ptr s, shared_ptr c, c->flags |= flags_for_version(c->version, cmd.sub_version); - uint32_t serial_number = strtoul(cmd.serial_number, nullptr, 16); + uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16); try { if (c->version == GameVersion::GC) { - c->license = s->license_manager->verify_gc(serial_number, cmd.access_key, - nullptr); + c->license = s->license_manager->verify_gc(serial_number, + cmd.access_key.c_str(), nullptr); } else { - c->license = s->license_manager->verify_pc(serial_number, cmd.access_key, - nullptr); + c->license = s->license_manager->verify_pc(serial_number, + cmd.access_key.c_str(), nullptr); } } catch (const exception& e) { // See comment in 9A handler about why we do this even if unregistered users @@ -357,7 +360,8 @@ void process_login_bb(shared_ptr s, shared_ptr c, c->flags |= flags_for_version(c->version, 0); try { - c->license = s->license_manager->verify_bb(cmd.username, cmd.password); + c->license = s->license_manager->verify_bb( + cmd.username.c_str(), cmd.password.c_str()); } catch (const exception& e) { u16string message = u"Login failed: " + decode_sjis(e.what()); send_message_box(c, message.c_str()); @@ -770,21 +774,18 @@ void process_menu_selection(shared_ptr s, shared_ptr c, } if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES)) { - char16_t password[0x10]; - memset(password, 0, sizeof(password)); + ptext password; if (data.size() > sizeof(C_MenuSelection)) { if (uses_unicode) { size_t max_chars = (data.size() - sizeof(C_MenuSelection)) / sizeof(char16_t); - strcpy_z(password, cmd.password.pcbb, - min(max_chars, countof(password))); + password.assign(cmd.password.pcbb, max_chars); } else { size_t max_chars = (data.size() - sizeof(C_MenuSelection)) / sizeof(char); - decode_sjis(password, cmd.password.dcgc, - min(max_chars, countof(password))); + password = decode_sjis(cmd.password.dcgc, max_chars); } } - if (game->password[0] && char16ncmp(game->password, password, 0x10)) { + if (!game->password.empty() && (game->password != password)) { send_message_box(c, u"$C6Incorrect password."); break; } @@ -1036,7 +1037,7 @@ void process_player_data(shared_ptr s, shared_ptr c, switch (c->version) { case GameVersion::PC: check_size(data.size(), sizeof(PSOPlayerDataPC), - sizeof(PSOPlayerDataPC) + sizeof(char16_t) * countof(c->player.auto_reply)); + sizeof(PSOPlayerDataPC) + sizeof(char16_t) * c->player.auto_reply.size()); c->player.import(*reinterpret_cast(data.data())); break; case GameVersion::GC: @@ -1045,13 +1046,13 @@ void process_player_data(shared_ptr s, shared_ptr c, // TODO: import Episode 3 data somewhere } else { check_size(data.size(), sizeof(PSOPlayerDataGC), - sizeof(PSOPlayerDataGC) + sizeof(char) * countof(c->player.auto_reply)); + sizeof(PSOPlayerDataGC) + sizeof(char) * c->player.auto_reply.size()); } c->player.import(*reinterpret_cast(data.data())); break; case GameVersion::BB: check_size(data.size(), sizeof(PSOPlayerDataBB), - sizeof(PSOPlayerDataBB) + sizeof(char16_t) * countof(c->player.auto_reply)); + sizeof(PSOPlayerDataBB) + sizeof(char16_t) * c->player.auto_reply.size()); c->player.import(*reinterpret_cast(data.data())); break; default: @@ -1148,7 +1149,7 @@ void process_chat_generic(shared_ptr s, shared_ptr c, continue; } send_chat_message(l->clients[x], c->license->serial_number, - c->player.disp.name, processed_text.c_str()); + c->player.disp.name.data(), processed_text.c_str()); } } } @@ -1157,7 +1158,7 @@ void process_chat_pc_bb(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // 06 const auto& cmd = check_size_t(data, sizeof(C_Chat_06), 0xFFFF); u16string text(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t)); - text.resize(char16len(text.c_str())); + strip_trailing_zeroes(text); process_chat_generic(s, c, text); } @@ -1248,13 +1249,13 @@ void process_create_character_bb(shared_ptr s, shared_ptr c send_message_box(c, u"$C6You are not logged in."); return; } - if (c->player.disp.name[0]) { + if (!c->player.disp.name.empty()) { send_message_box(c, u"$C6You have already loaded a character."); return; } c->bb_player_index = cmd.player_index; - snprintf(c->player.bank_name, 0x20, "player%" PRIu32, cmd.player_index + 1); + c->player.bank_name = string_printf("player%" PRIu32, cmd.player_index + 1); string player_filename = filename_for_player_bb(c->license->username, cmd.player_index); string bank_filename = filename_for_bank_bb(c->license->username, c->player.bank_name); string template_filename = filename_for_class_template_bb(cmd.preview.char_class); @@ -1305,27 +1306,27 @@ void process_change_account_data_bb(shared_ptr, shared_ptr break; case 0x02ED: check_size(data.size(), sizeof(cmd->symbol_chats)); - memcpy(c->player.symbol_chats, cmd->symbol_chats, 0x04E0); + c->player.symbol_chats = cmd->symbol_chats; break; case 0x03ED: check_size(data.size(), sizeof(cmd->chat_shortcuts)); - memcpy(c->player.shortcuts, cmd->chat_shortcuts, 0x0A40); + c->player.shortcuts = cmd->chat_shortcuts; break; case 0x04ED: check_size(data.size(), sizeof(cmd->key_config)); - memcpy(&c->player.key_config.key_config, cmd->key_config, 0x016C); + c->player.key_config.key_config = cmd->key_config; break; case 0x05ED: check_size(data.size(), sizeof(cmd->pad_config)); - memcpy(&c->player.key_config.joystick_config, cmd->pad_config, 0x0038); + c->player.key_config.joystick_config = cmd->pad_config; break; case 0x06ED: check_size(data.size(), sizeof(cmd->tech_menu)); - memcpy(&c->player.tech_menu_config, cmd->tech_menu, 0x0028); + c->player.tech_menu_config = cmd->tech_menu; break; case 0x07ED: check_size(data.size(), sizeof(cmd->customize)); - memcpy(c->player.disp.config, cmd->customize, 0xE8); + c->player.disp.config = cmd->customize; break; default: throw invalid_argument("unknown account command"); @@ -1386,22 +1387,21 @@ void process_simple_mail(shared_ptr s, shared_ptr c, // If the sender is blocked, don't forward the mail for (size_t y = 0; y < 30; y++) { - if (target->player.blocked[y] == c->license->serial_number) { + if (target->player.blocked_senders.data()[y] == c->license->serial_number) { return; } } // If the target has auto-reply enabled, send the autoreply - if (target->player.auto_reply[0]) { + if (!target->player.auto_reply.empty()) { send_simple_mail(c, target->license->serial_number, - target->player.disp.name, target->player.auto_reply); + target->player.disp.name.c_str(), target->player.auto_reply.c_str()); } // Forward the message - string message(cmd.text, strnlen(cmd.text, sizeof(cmd.text) / sizeof(cmd.text[0]))); - u16string u16message = decode_sjis(message); - send_simple_mail(target, c->license->serial_number, c->player.disp.name, - u16message.data()); + u16string u16message = decode_sjis(cmd.text); + send_simple_mail(target, c->license->serial_number, + c->player.disp.name.c_str(), u16message.c_str()); } @@ -1418,33 +1418,31 @@ void process_info_board_request(shared_ptr s, shared_ptr c, template void process_write_info_board_t(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // D9 - check_size(data.size(), 0, countof(c->player.info_board) * sizeof(CharT)); - memset(c->player.info_board, 0, sizeof(c->player.info_board)); - strncpy_t(c->player.info_board, reinterpret_cast(data.data()), - min(countof(c->player.info_board), data.size() / sizeof(CharT))); + check_size(data.size(), 0, c->player.info_board.size() * sizeof(CharT)); + c->player.info_board.assign( + reinterpret_cast(data.data()), + data.size() / sizeof(CharT)); } template void process_set_auto_reply_t(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C7 - check_size(data.size(), 0, countof(c->player.auto_reply) * sizeof(CharT)); - memset(c->player.auto_reply, 0, sizeof(c->player.auto_reply)); - strncpy_t(c->player.auto_reply, reinterpret_cast(data.data()), - min(countof(c->player.auto_reply), data.size() / sizeof(CharT))); + check_size(data.size(), 0, c->player.auto_reply.size() * sizeof(CharT)); + c->player.auto_reply.assign( + reinterpret_cast(data.data()), + data.size() / sizeof(CharT)); } void process_disable_auto_reply(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C8 check_size(data.size(), 0); - memset(c->player.auto_reply, 0, sizeof(c->player.auto_reply)); + c->player.auto_reply.clear(); } -void process_set_blocked_list(shared_ptr, shared_ptr c, +void process_set_blocked_senders_list(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C6 const auto& cmd = check_size_t(data); - for (size_t x = 0; x < countof(cmd.blocked_senders); x++) { - c->player.blocked[x] = cmd.blocked_senders[x]; - } + c->player.blocked_senders = cmd.blocked_senders; } @@ -1501,8 +1499,8 @@ shared_ptr create_game_generic(shared_ptr s, } shared_ptr game(new Lobby()); - strcpy_z(game->name, name, countof(game->name)); - strcpy_z(game->password, password, countof(game->name)); + game->name = name; + game->password = password; game->version = c->version; game->section_id = c->override_section_id >= 0 ? c->override_section_id : c->player.disp.section_id; @@ -1548,8 +1546,9 @@ shared_ptr create_game_generic(shared_ptr s, try { auto filename = string_printf( "system/blueburst/map/%c%hhu%zu%" PRIu32 "%" PRIu32 ".dat", - *type_char, game->episode, x, game->variations[x * 2], - game->variations[(x * 2) + 1]); + *type_char, game->episode, x, + game->variations.data()[x * 2].load(), + game->variations.data()[(x * 2) + 1].load()); game->enemies = load_map(filename.c_str(), game->episode, game->difficulty, bp_subtable, false); break; @@ -1566,8 +1565,9 @@ shared_ptr create_game_generic(shared_ptr s, for (size_t x = 0; x < 0x10; x++) { auto filename = string_printf( "system/blueburst/map/m%hhu%zu%" PRIu32 "%" PRIu32 ".dat", - game->episode, x, game->variations[x * 2], - game->variations[(x * 2) + 1]); + game->episode, x, + game->variations.data()[x * 2].load(), + game->variations.data()[(x * 2) + 1].load()); game->enemies = load_map(filename.c_str(), game->episode, game->difficulty, bp_subtable, false); } @@ -1583,11 +1583,11 @@ shared_ptr create_game_generic(shared_ptr s, if (variation_maxes) { for (size_t x = 0; x < 0x20; x++) { - game->variations[x] = random_int(0, variation_maxes[x] - 1); + game->variations.data()[x] = random_int(0, variation_maxes[x] - 1); } } else { for (size_t x = 0; x < 0x20; x++) { - game->variations[x] = 0; + game->variations.data()[x] = 0; } } @@ -1598,7 +1598,7 @@ void process_create_game_pc(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // C1 const auto& cmd = check_size_t(data); - auto game = create_game_generic(s, c, cmd.name, cmd.password, 1, + auto game = create_game_generic(s, c, cmd.name.c_str(), cmd.password.c_str(), 1, cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, 0); s->add_lobby(game); @@ -1639,7 +1639,7 @@ void process_create_game_bb(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { // C1 const auto& cmd = check_size_t(data); - auto game = create_game_generic(s, c, cmd.name, cmd.password, + auto game = create_game_generic(s, c, cmd.name.c_str(), cmd.password.c_str(), cmd.episode, cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, cmd.solo_mode); @@ -1657,7 +1657,7 @@ void process_lobby_name_request(shared_ptr s, shared_ptr c, if (!l) { throw invalid_argument("client not in any lobby"); } - send_lobby_name(c, l->name); + send_lobby_name(c, l->name.c_str()); } void process_client_ready(shared_ptr s, shared_ptr c, @@ -1712,7 +1712,8 @@ and operated completely independently.\n\ \n\ License check: "; try { - c->license = s->license_manager->verify_bb(cmd.username, cmd.password); + c->license = s->license_manager->verify_bb( + cmd.username.c_str(), cmd.password.c_str()); message += u"OK"; } catch (const exception& e) { message += decode_sjis(e.what()); @@ -1817,7 +1818,7 @@ static process_command_t dc_handlers[0x100] = { // C0 nullptr, process_create_game_dc_gc, nullptr, nullptr, - nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t, + nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t, process_disable_auto_reply, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, @@ -1900,7 +1901,7 @@ static process_command_t pc_handlers[0x100] = { // C0 nullptr, process_create_game_pc, nullptr, nullptr, - nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t, + nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t, process_disable_auto_reply, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, @@ -1985,7 +1986,7 @@ static process_command_t gc_handlers[0x100] = { // C0 process_choice_search, process_create_game_dc_gc, nullptr, nullptr, - nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t, + nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t, process_disable_auto_reply, process_game_command, process_ep3_server_data_request, process_game_command, nullptr, nullptr, nullptr, nullptr, @@ -2072,7 +2073,7 @@ static process_command_t bb_handlers[0x100] = { // C0 nullptr, process_create_game_bb, nullptr, nullptr, - nullptr, nullptr, process_set_blocked_list, process_set_auto_reply_t, + nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t, process_disable_auto_reply, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 806aa8f4..415dd926 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -114,7 +114,7 @@ static void process_subcommand_send_guild_card(shared_ptr, if (count < 0x25) { return; } - decode_sjis(c->player.guild_card_desc, + c->player.guild_card_desc = decode_sjis( reinterpret_cast(&p[9].byte[0]), 0x58); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 8bc90088..b7ed6a9e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -156,12 +156,11 @@ S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc( uint32_t server_key, uint32_t client_key) { S_ServerInit_DC_GC_02_17 cmd; - memset(&cmd, 0, sizeof(cmd)); - - strcpy(cmd.copyright, initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright); + cmd.copyright = initial_connection + ? dc_port_map_copyright : dc_lobby_server_copyright; cmd.server_key = server_key; cmd.client_key = client_key; - strcpy(cmd.after_message, anti_copyright); + cmd.after_message = anti_copyright; return cmd; } @@ -194,17 +193,16 @@ void send_server_init_dc_pc_gc(shared_ptr c, void send_server_init_bb(shared_ptr s, shared_ptr c) { S_ServerInit_BB_03 cmd; - memset(&cmd, 0, sizeof(cmd)); - strcpy(cmd.copyright, bb_game_server_copyright); - random_data(cmd.server_key, 0x30); - random_data(cmd.client_key, 0x30); - strcpy(cmd.after_message, anti_copyright); + cmd.copyright = bb_game_server_copyright; + random_data(cmd.server_key.data(), cmd.server_key.bytes()); + random_data(cmd.client_key.data(), cmd.client_key.bytes()); + cmd.after_message = anti_copyright; send_command(c, 0x03, 0x00, cmd); - c->crypt_out.reset(new PSOBBEncryption(s->default_key_file, cmd.server_key, - sizeof(cmd.server_key))); - c->crypt_in.reset(new PSOBBEncryption(s->default_key_file, cmd.client_key, - sizeof(cmd.client_key))); + c->crypt_out.reset(new PSOBBEncryption(s->default_key_file, + cmd.server_key.data(), cmd.server_key.bytes())); + c->crypt_in.reset(new PSOBBEncryption(s->default_key_file, + cmd.client_key.data(), cmd.client_key.bytes())); } void send_server_init_patch(shared_ptr c) { @@ -212,8 +210,7 @@ void send_server_init_patch(shared_ptr c) { uint32_t client_key = random_object(); S_ServerInit_Patch_02 cmd; - memset(&cmd, 0, sizeof(cmd)); - strcpy(cmd.copyright, patch_server_copyright); + cmd.copyright = patch_server_copyright; cmd.server_key = server_key; cmd.client_key = client_key; send_command(c, 0x02, 0x00, cmd); @@ -246,7 +243,6 @@ void send_server_init(shared_ptr s, shared_ptr c, // for non-BB clients, updates the client's guild card and security data void send_update_client_config(shared_ptr c) { S_UpdateClientConfig_DC_PC_GC_04 cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.player_tag = 0x00010000; cmd.guild_card_number = c->license->serial_number; cmd.cfg = c->export_config(); @@ -265,7 +261,6 @@ void send_reconnect(shared_ptr c, uint32_t address, uint16_t port) { void send_pc_gc_split_reconnect(shared_ptr c, uint32_t address, uint16_t pc_port, uint16_t gc_port) { S_ReconnectSplit_19 cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.pc_address = address; cmd.pc_port = pc_port; cmd.gc_command = 0x19; @@ -352,7 +347,7 @@ void send_stream_file_bb(shared_ptr c) { uint32_t buffer_offset = 0; for (size_t x = 0; x < entry_count; x++) { - auto filename = string_printf("system/blueburst/%s", entries[x].filename); + auto filename = string_printf("system/blueburst/%s", entries[x].filename.c_str()); auto file_data = file_cache.get(filename); size_t file_data_remaining = file_data->size(); @@ -366,11 +361,12 @@ void send_stream_file_bb(shared_ptr c) { } memcpy(&chunk_cmd.data[buffer_offset], file_data->data() + file_data->size() - file_data_remaining, read_size); + // TODO: We probably should clear the rest of the buffer on the last chunk buffer_offset += read_size; file_data_remaining -= read_size; if (buffer_offset == 0x6800) { - // note: the client sends 0x03EB in response to these, but we'll just + // Note: the client sends 0x03EB in response to these, but we'll just // ignore them because we don't need any of the contents send_command(c, 0x02EB, 0x00000000, chunk_cmd); buffer_offset = 0; @@ -399,9 +395,7 @@ void send_complete_player_bb(shared_ptr c) { // patch functions void send_check_directory_patch(shared_ptr c, const char* dir) { - S_CheckDirectory_Patch_09 cmd; - memset(&cmd, 0, sizeof(cmd)); - strncpy_t(cmd.name, dir, countof(cmd.name)); + S_CheckDirectory_Patch_09 cmd = {dir}; send_command(c, 0x09, 0x00, cmd); } @@ -416,7 +410,7 @@ void send_text(shared_ptr c, StringWriter& w, uint16_t command, string data = encode_sjis(text); add_color(w, data.c_str(), data.size()); } else { - add_color(w, text, char16len(text)); + add_color(w, text, text_strlen_t(text)); } while (w.str().size() & 3) { w.put_u8(0); @@ -493,12 +487,11 @@ void send_chat_message(shared_ptr c, uint32_t from_serial_number, void send_simple_mail_gc(std::shared_ptr c, uint32_t from_serial_number, const char16_t* from_name, const char16_t* text) { SC_SimpleMail_GC_81 cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.player_tag = 0x00010000; cmd.from_serial_number = from_serial_number; - encode_sjis(cmd.from_name, from_name, sizeof(cmd.from_name) / sizeof(cmd.from_name[0])); + cmd.from_name = from_name; cmd.to_serial_number = c->license->serial_number; - encode_sjis(cmd.text, text, sizeof(cmd.text) / sizeof(cmd.text[0])); + cmd.text = text; send_command(c, 0x81, 0x00, cmd); } @@ -523,12 +516,10 @@ void send_info_board_t(shared_ptr c, shared_ptr l) { if (!c.get()) { continue; } - entries.emplace_back(); - auto& e = entries.back(); - memset(&e, 0, sizeof(e)); - strncpy_t(e.name, c->player.disp.name, countof(e.name)); - strncpy_t(e.message, c->player.info_board, countof(e.message)); - add_color_inplace(e.message, countof(e.message)); + auto& e = entries.emplace_back(); + e.name = c->player.disp.name; + e.message = c->player.info_board; + add_color_inplace(e.message); } send_command(c, 0xD8, entries.size(), entries); } @@ -554,7 +545,6 @@ void send_card_search_result_t( shared_ptr result, shared_ptr result_lobby) { S_GuildCardSearchResult cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.player_tag = 0x00010000; cmd.searcher_serial_number = c->license->serial_number; cmd.result_serial_number = result->license->serial_number; @@ -570,18 +560,18 @@ void send_card_search_result_t( cmd.reconnect_command.unused = 0; auto encoded_server_name = encode_sjis(s->name); + string location_string; if (result_lobby->is_game()) { string encoded_lobby_name = encode_sjis(result_lobby->name); - snprintf(cmd.location_string, sizeof(cmd.location_string), - "%s,Block 00,,%s", encoded_lobby_name.c_str(), encoded_server_name.c_str()); + location_string = string_printf("%s,Block 00,,%s", + encoded_lobby_name.c_str(), encoded_server_name.c_str()); } else { - snprintf(cmd.location_string, sizeof(cmd.location_string), "Block 00,,%s", - encoded_server_name.c_str()); + location_string = string_printf("Block 00,,%s", encoded_server_name.c_str()); } + cmd.location_string = location_string; cmd.menu_id = LOBBY_MENU_ID; cmd.lobby_id = result->lobby_id; - memset(cmd.unused, 0, sizeof(cmd.unused)); - strncpy_t(cmd.name, result->player.disp.name, countof(cmd.name)); + cmd.name = result->player.disp.name; send_command(c, 0x41, 0x00, cmd); } @@ -605,26 +595,22 @@ void send_card_search_result( } } -//////////////////////////////////////////////////////////////////////////////// -// CommandSendGuildCard: generates a guild card for the source player and sends it to the destination player + void send_guild_card_gc(shared_ptr c, shared_ptr source) { S_SendGuildCard_GC cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.subcommand = 0x06; cmd.subsize = 0x25; cmd.unused = 0x0000; cmd.player_tag = 0x00010000; cmd.reserved1 = 1; cmd.reserved2 = 1; - cmd.serial_number = source->license->serial_number; - encode_sjis(cmd.name, source->player.disp.name, countof(cmd.name)); + cmd.name = source->player.disp.name; remove_language_marker_inplace(cmd.name); - encode_sjis(cmd.desc, source->player.guild_card_desc, countof(cmd.desc)); + cmd.desc = source->player.guild_card_desc; cmd.section_id = source->player.disp.section_id; cmd.char_class = source->player.disp.char_class; - send_command(c, 0x62, c->lobby_client_id, cmd); } @@ -636,16 +622,12 @@ void send_guild_card_bb(shared_ptr c, shared_ptr source) { cmd.unused = 0x0000; cmd.reserved1 = 1; cmd.reserved2 = 1; - cmd.serial_number = source->license->serial_number; - strcpy_z(cmd.name, source->player.disp.name, countof(cmd.name)); - remove_language_marker_inplace(cmd.name); - strcpy_z(cmd.team_name, source->player.team_name, countof(cmd.team_name)); - remove_language_marker_inplace(cmd.team_name); - strcpy_z(cmd.desc, source->player.guild_card_desc, countof(cmd.desc)); + cmd.name = remove_language_marker(source->player.disp.name); + cmd.team_name = remove_language_marker(source->player.team_name); + cmd.desc = source->player.guild_card_desc; cmd.section_id = source->player.disp.section_id; cmd.char_class = source->player.disp.char_class; - send_command(c, 0x62, c->lobby_client_id, cmd); } @@ -673,14 +655,12 @@ void send_menu_t( bool is_info_menu) { vector entries; - entries.emplace_back(); { - auto& e = entries.back(); - memset(&e, 0, sizeof(e)); + auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = 0xFFFFFFFF; e.flags = 0x0004; - strncpy_t(e.text, menu_name, countof(e.text)); + e.text = menu_name; } for (const auto& item : items) { @@ -692,14 +672,11 @@ void send_menu_t( ((item.flags & MenuItemFlag::REQUIRES_MESSAGE_BOXES) && (c->flags & ClientFlag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION))) { continue; } - - entries.emplace_back(); - auto& e = entries.back(); - memset(&e, 0, sizeof(e)); + auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = item.item_id; e.flags = (c->version == GameVersion::BB) ? 0x0004 : 0x0F04; - strncpy_t(e.text, item.name.c_str(), countof(e.text)); + e.text = item.name; } send_command(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries); @@ -722,43 +699,38 @@ template void send_game_menu_t(shared_ptr c, shared_ptr s) { vector> entries; { - entries.emplace_back(); - auto& e = entries.back(); - memset(&e, 0, sizeof(e)); + auto& e = entries.emplace_back(); e.menu_id = GAME_MENU_ID; e.game_id = 0x00000000; e.difficulty_tag = 0x00; e.num_players = 0x00; - strncpy_t(e.name, s->name.c_str(), countof(e.name)); + e.name = s->name; e.episode = 0x00; e.flags = 0x04; } for (shared_ptr l : s->all_lobbies()) { - if (!l->is_game()) { + if (!l->is_game() || (l->version != c->version)) { continue; } - if (l->version != c->version) { - continue; - } - if (!(l->flags & LobbyFlag::EPISODE_3) != !(c->flags & ClientFlag::EPISODE_3_GAMES)) { + bool l_is_ep3 = !!(l->flags & LobbyFlag::EPISODE_3); + bool c_is_ep3 = !!(c->flags & ClientFlag::EPISODE_3_GAMES); + if (l_is_ep3 != c_is_ep3) { continue; } - entries.emplace_back(); - auto& e = entries.back(); + auto& e = entries.emplace_back(); memset(&e, 0, sizeof(e)); e.menu_id = GAME_MENU_ID; e.game_id = l->lobby_id; - e.difficulty_tag = ((l->flags & LobbyFlag::EPISODE_3) - ? 0x0A : (l->difficulty + 0x22)); + e.difficulty_tag = (l_is_ep3 ? 0x0A : (l->difficulty + 0x22)); e.num_players = l->count_clients(); e.episode = ((c->version == GameVersion::BB) ? (l->max_clients << 4) : 0) | l->episode; if (l->flags & LobbyFlag::EPISODE_3) { - e.flags = (l->password[0] ? 2 : 0); + e.flags = (l->password.empty() ? 0 : 2); } else { - e.flags = ((l->episode << 6) | ((l->mode % 3) << 4) | (l->password[0] ? 2 : 0)) | ((l->mode == 3) ? 4 : 0); + e.flags = ((l->episode << 6) | ((l->mode % 3) << 4) | (l->password.empty() ? 0 : 2)) | ((l->mode == 3) ? 4 : 0); } - strncpy_t(e.name, l->name, countof(e.name)); + e.name = l->name; } send_command(c, 0x08, entries.size() - 1, entries); @@ -782,14 +754,12 @@ void send_quest_menu_t( bool is_download_menu) { vector entries; for (const auto& quest : quests) { - entries.emplace_back(); - auto& e = entries.back(); - memset(&e, 0, sizeof(e)); + auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = quest->quest_id; - strncpy_t(e.name, quest->name.c_str(), countof(e.name)); - strncpy_t(e.short_desc, quest->short_description.c_str(), countof(e.short_desc)); - add_color_inplace(e.short_desc, countof(e.short_desc)); + e.name = quest->name; + e.short_desc = quest->short_description; + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); } @@ -802,14 +772,12 @@ void send_quest_menu_t( bool is_download_menu) { vector entries; for (const auto& item : items) { - entries.emplace_back(); - auto& e = entries.back(); - memset(&e, 0, sizeof(e)); + auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = item.item_id; - strncpy_t(e.name, item.name.c_str(), 0x20); - strncpy_t(e.short_desc, item.description.c_str(), 0x70); - add_color_inplace(e.short_desc, 0x70); + e.name = item.name; + e.short_desc = item.description; + add_color_inplace(e.short_desc); } send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); } @@ -853,9 +821,7 @@ void send_lobby_list(shared_ptr c, shared_ptr s) { if ((l->flags & LobbyFlag::EPISODE_3) && !(c->flags & ClientFlag::EPISODE_3_GAMES)) { continue; } - - entries.emplace_back(); - auto& e = entries.back(); + auto& e = entries.emplace_back(); e.menu_id = LOBBY_MENU_ID; e.item_id = l->lobby_id; e.unused = 0; @@ -872,10 +838,10 @@ void send_lobby_list(shared_ptr c, shared_ptr s) { template void send_join_game_t(shared_ptr c, shared_ptr l) { S_JoinGame cmd; - memset(&cmd, 0, sizeof(cmd)); + + cmd.variations = l->variations; size_t player_count = 0; - memcpy(cmd.variations, l->variations, sizeof(cmd.variations)); for (size_t x = 0; x < 4; x++) { if (l->clients[x]) { cmd.lobby_data[x].player_tag = 0x00010000; @@ -883,8 +849,7 @@ void send_join_game_t(shared_ptr c, shared_ptr l) { // See comment in send_join_lobby_t about Episode III behavior here cmd.lobby_data[x].ip_address = 0x7F000001; cmd.lobby_data[x].client_id = c->lobby_client_id; - strncpy_t(cmd.lobby_data[x].name, l->clients[x]->player.disp.name, - countof(cmd.lobby_data[x].name)); + cmd.lobby_data[x].name = l->clients[x]->player.disp.name; if (l->flags & LobbyFlag::EPISODE_3) { cmd.players_ep3[x].inventory = l->clients[x]->player.inventory; cmd.players_ep3[x].disp = convert_player_disp_data( @@ -927,7 +892,6 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, } } else { command = joining_client ? 0x68 : 0x67; - } uint8_t lobby_type = (l->type > 14) ? (l->block - 1) : l->type; @@ -949,7 +913,6 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, } S_JoinLobby cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.client_id = c->lobby_client_id; cmd.leader_id = l->leader_id; cmd.disable_udp = 0x01; @@ -972,7 +935,6 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, size_t used_entries = 0; for (const auto& lc : lobby_clients) { auto& e = cmd.entries[used_entries++]; - memset(&e.lobby_data, 0, sizeof(e.lobby_data)); e.lobby_data.player_tag = 0x00010000; e.lobby_data.guild_card = lc->license->serial_number; // There's a strange behavior (bug? "feature"?) in Episode 3 where the start @@ -981,8 +943,7 @@ void send_join_lobby_t(shared_ptr c, shared_ptr l, // to avoid this behavior. e.lobby_data.ip_address = 0x7F000001; e.lobby_data.client_id = lc->lobby_client_id; - strncpy_t(e.lobby_data.name, lc->player.disp.name, - countof(e.lobby_data.name)); + e.lobby_data.name = lc->player.disp.name; e.inventory = lc->player.inventory; e.disp = convert_player_disp_data(lc->player.disp); if (c->version == GameVersion::PC) { @@ -1059,9 +1020,7 @@ void send_arrow_update(shared_ptr l) { if (!l->clients[x]) { continue; } - - entries.emplace_back(); - auto& e = entries.back(); + auto& e = entries.emplace_back(); e.player_tag = 0x00010000; e.serial_number = l->clients[x]->license->serial_number; e.arrow_color = l->clients[x]->lobby_arrow_color; @@ -1092,16 +1051,14 @@ void send_player_stats_change(shared_ptr l, shared_ptr c, vector subs; while (amount > 0) { { - subs.emplace_back(); - auto& sub = subs.back(); + auto& sub = subs.emplace_back(); sub.byte[0] = 0x9A; sub.byte[1] = 0x02; sub.byte[2] = c->lobby_client_id; sub.byte[3] = 0x00; } { - subs.emplace_back(); - auto& sub = subs.back(); + auto& sub = subs.emplace_back(); sub.byte[0] = 0x00; sub.byte[1] = 0x00; sub.byte[2] = stat; @@ -1288,7 +1245,6 @@ void send_ep3_card_list_update(shared_ptr c) { StringWriter w; w.put_u32l(file_data->size()); w.write(*file_data); - w.str().resize((w.str().size() + 3) & (~3)); send_command(c, 0xB8, 0x00, w.str()); } @@ -1340,11 +1296,10 @@ void send_quest_open_file_t( bool is_download_quest, bool is_ep3_quest) { CommandT cmd; - memset(&cmd, 0, sizeof(cmd)); cmd.flags = 2 + is_ep3_quest; cmd.file_size = file_size; - strncpy_t(cmd.name, filename.c_str(), countof(cmd.name)); - strncpy_t(cmd.filename, filename.c_str(), countof(cmd.filename)); + cmd.name = filename.c_str(); + cmd.filename = filename.c_str(); send_command(c, is_download_quest ? 0xA6 : 0x44, 0x00, cmd); } @@ -1360,8 +1315,7 @@ void send_quest_file_chunk( } S_WriteFile_13_A7 cmd; - memset(cmd.filename, 0, countof(cmd.filename)); - strncpy_t(cmd.filename, filename, countof(cmd.filename)); + cmd.filename = filename; memcpy(cmd.data, data, size); if (size < 0x400) { memset(&cmd.data[size], 0, 0x400 - size); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index abe8871f..2a30be2f 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -149,25 +149,25 @@ Proxy commands (these will only work when exactly one client is connected):\n\ if (token.size() >= 29) { throw invalid_argument("username too long"); } - strcpy(l->username, token.c_str() + 9); + l->username = token.substr(9); } else if (starts_with(token, "bb-password=")) { if (token.size() >= 32) { throw invalid_argument("bb-password too long"); } - strcpy(l->bb_password, token.c_str() + 12); + l->bb_password = token.substr(12); } else if (starts_with(token, "gc-password=")) { if (token.size() > 20) { throw invalid_argument("gc-password too long"); } - strcpy(l->gc_password, token.c_str() + 12); + l->gc_password = token.substr(12); } else if (starts_with(token, "access-key=")) { if (token.size() > 23) { throw invalid_argument("access-key is too long"); } - strcpy(l->access_key, token.c_str() + 11); + l->access_key = token.substr(11); } else if (starts_with(token, "serial=")) { l->serial_number = stoul(token.substr(7)); diff --git a/src/ServerState.cc b/src/ServerState.cc index 41e2a1ce..2b70dcca 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -33,7 +33,7 @@ ServerState::ServerState() (is_ep3_only ? LobbyFlag::EPISODE_3 : 0); l->block = x + 1; l->type = x; - strcpy_z(l->name, lobby_name.c_str(), 0x24); + l->name = lobby_name; l->max_clients = 12; this->add_lobby(l); diff --git a/src/Text.cc b/src/Text.cc index fcb1b52c..0b149868 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -30,20 +30,14 @@ int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count) { return 0; } -size_t char16len(const char16_t* s) { - size_t x; - for (x = 0; s[x] != 0; x++); - return x; -} - static vector unicode_to_sjis_table_data; static vector sjis_to_unicode_table_data; static void load_sjis_tables() { - unicode_to_sjis_table_data.resize(0x10000); - sjis_to_unicode_table_data.resize(0x10000); + unicode_to_sjis_table_data.resize(0x10000, 0); + sjis_to_unicode_table_data.resize(0x10000, 0); // TODO: this is inefficient; it makes multiple copies of the string auto file_contents = load_file("system/sjis-table.ini"); @@ -75,166 +69,107 @@ static const vector& unicode_to_sjis_table() { return unicode_to_sjis_table_data; } -// TODO: It looks like these functions are probably wrong. Specifically, we -// don't write the high byte when encoding non-ASCII chars, do we? -void encode_sjis(char* dest, const char16_t* source, size_t max) { - const auto& table = unicode_to_sjis_table(); - while (*source && (--max)) { - *(dest++) = table[*(source++)]; - }; - *dest = 0; -} - -void decode_sjis(char16_t* dest, const char* source, size_t max) { - const auto& table = sjis_to_unicode_table(); - while (*source && (--max)) { - char16_t src_char = *(source++); - if (src_char & 0x80) { - src_char = (src_char << 8) | *(source++); - if ((src_char & 0xFF) == 0) { - return; - } - } - *(dest++) = table[src_char]; - }; - *dest = 0; -} std::string encode_sjis(const char16_t* src, size_t src_count) { const auto& table = unicode_to_sjis_table(); + + const char16_t* src_end = src + src_count; string ret; - for (; *src && (src_count > 0); src_count--) { - ret.push_back(table[*(src++)]); + while ((src != src_end) && *src) { + uint16_t ch = *(src++); + uint16_t translated_c = table[ch]; + if (translated_c == 0) { + throw runtime_error("untranslatable unicode character"); + } else if (translated_c & 0xFF00) { + ret.push_back((translated_c >> 8) & 0xFF); + ret.push_back(translated_c & 0xFF); + } else { + ret.push_back(translated_c & 0xFF); + } }; return ret; } +void encode_sjis( + char* dest, + size_t dest_count, + const char16_t* src, + size_t src_count) { + const auto& table = unicode_to_sjis_table(); + + if (dest_count == 0) { + throw logic_error("cannot encode into zero-length buffer"); + } + + const char16_t* src_end = src + src_count; + const char* dest_end = dest + (dest_count - 1); + while ((dest != dest_end) && (src != src_end) && *src) { + uint16_t ch = *(src++); + uint16_t translated_c = table[ch]; + if (translated_c == 0) { + throw runtime_error("untranslatable unicode character"); + } else if (translated_c & 0xFF00) { + *(dest++) = (translated_c >> 8) & 0xFF; + // If the second byte of this character would cause the null to overrun + // the buffer, erase the first byte instead and return early + if (dest == dest_end) { + *(dest - 1) = 0; + } else { + *(dest++) = translated_c & 0xFF; + } + } else { + *(dest++) = translated_c & 0xFF; + } + } + *dest = 0; +} + std::u16string decode_sjis(const char* src, size_t src_count) { const auto& table = sjis_to_unicode_table(); + + const char* src_end = src + src_count; u16string ret; - while (*src && (src_count > 0)) { - char16_t src_char = *(src++); - src_count--; + while ((src != src_end) && *src) { + uint16_t src_char = *(src++); if (src_char & 0x80) { - if (src_count == 0) { - return ret; + if (src == src_end) { + throw runtime_error("incomplete extended character"); } src_char = (src_char << 8) | *(src++); if ((src_char & 0xFF) == 0) { - return ret; + throw runtime_error("incomplete extended character"); } - src_count--; } ret.push_back(table[src_char]); }; return ret; } -std::string encode_sjis(const std::u16string& source) { - const auto& table = unicode_to_sjis_table(); - string ret; - for (char16_t ch : source) { - ret.push_back(table[ch]); - }; - return ret; -} - -std::u16string decode_sjis(const std::string& source) { +void decode_sjis( + char16_t* dest, + size_t dest_count, + const char* src, + size_t src_count) { const auto& table = sjis_to_unicode_table(); - u16string ret; - for (size_t x = 0; x < source.size();) { - char16_t src_char = source[x++]; + + if (dest_count == 0) { + throw logic_error("cannot decode into zero-length buffer"); + } + + const char* src_end = src + src_count; + const char16_t* dest_end = dest + (dest_count - 1); + while ((dest != dest_end) && (src != src_end) && *src) { + uint16_t src_char = *(src++); if (src_char & 0x80) { - if (x == source.size()) { - return ret; + if (src == src_end) { + throw runtime_error("incomplete extended character"); } - src_char = (src_char << 8) | source[x++]; + src_char = (src_char << 8) | *(src++); if ((src_char & 0xFF) == 0) { - return ret; + throw runtime_error("incomplete extended character"); } } - ret.push_back(table[src_char]); + *(dest++) = table[src_char]; }; - return ret; -} - - - -void add_language_marker_inplace(char* a, char e, size_t dest_count) { - if ((a[0] == '\t') && (a[1] != 'C')) { - return; - } - - size_t existing_count = strlen(a); - if (existing_count > dest_count - 3) { - existing_count = dest_count - 3; - } - memmove(&a[2], a, (existing_count + 1) * sizeof(char)); - a[0] = '\t'; - a[1] = e; - a[existing_count + 2] = 0; -} - -void add_language_marker_inplace(char16_t* a, char16_t e, size_t dest_count) { - if ((a[0] == '\t') && (a[1] != 'C')) { - return; - } - - size_t existing_count = char16len(a); - if (existing_count > dest_count - 3) { - existing_count = dest_count - 3; - } - memmove(&a[2], a, (existing_count + 1) * sizeof(char16_t)); - a[0] = '\t'; - a[1] = e; - a[existing_count + 2] = 0; -} - -void remove_language_marker_inplace(char* a) { - if ((a[0] == '\t') && (a[1] != 'C')) { - strcpy(a, &a[2]); - } -} - -void remove_language_marker_inplace(char16_t* a) { - if ((a[0] == '\t') && (a[1] != 'C')) { - strcpy_z(a, &a[2], char16len(a) - 2); - } -} - -std::string add_language_marker(const std::string& s, char marker) { - if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) { - return s; - } - - string ret; - ret.push_back('\t'); - ret.push_back(marker); - return ret + s; -} - -std::u16string add_language_marker(const std::u16string& s, char16_t marker) { - if ((s.size() >= 2) && (s[0] == L'\t') && (s[1] != L'C')) { - return s; - } - - u16string ret; - ret.push_back(L'\t'); - ret.push_back(marker); - return ret + s; -} - -std::string remove_language_marker(const std::string& s) { - if ((s.size() < 2) || (s[0] != '\t') || (s[1] == 'C')) { - return s; - } - return s.substr(2); -} - -std::u16string remove_language_marker(const std::u16string& s) { - if ((s.size() < 2) || (s[0] != L'\t') || (s[1] == L'C')) { - return s; - } - return s.substr(2); } diff --git a/src/Text.hh b/src/Text.hh index be8a90c1..13d4b222 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -10,26 +10,74 @@ -#define countof(F) (sizeof(F) / sizeof(F[0])) +// TODO: delete these if not needed +// int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count); +// size_t char16len(const char16_t* s); -int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count); -size_t char16len(const char16_t* s); +// (1a) Conversion functions + +void encode_sjis( + char* dest, size_t dest_count, + const char16_t* src, size_t src_count); +void decode_sjis( + char16_t* dest, size_t dest_count, + const char* src, size_t src_count); -void encode_sjis(char* dest, const char16_t* source, size_t dest_count); -void decode_sjis(char16_t* dest, const char* source, size_t dest_count); std::string encode_sjis(const char16_t* source, size_t src_count); std::u16string decode_sjis(const char* source, size_t src_count); -std::string encode_sjis(const std::u16string& source); -std::u16string decode_sjis(const std::string& source); +inline std::string encode_sjis(const std::u16string& s) { + return encode_sjis(s.data(), s.size()); +} +inline std::u16string decode_sjis(const std::string& s) { + return decode_sjis(s.data(), s.size()); +} + +// (1b) Type-independent utility functions + +template +size_t text_strlen_t(const T* s) { + size_t ret = 0; + for (; s[ret] != 0; ret++) { } + return ret; +} + +template +size_t text_streq_t(const T* a, const T* b) { + for (;;) { + if (*a != *b) { + return false; + } + if (*a == 0) { + return true; + } + a++; + b++; + } +} + +template +size_t text_strneq_t(const T* a, const T* b, size_t count) { + for (; count; count--) { + if (*a != *b) { + return false; + } + if (*a == 0) { + return true; + } + a++; + b++; + } + return true; +} // Like strncpy, but *always* null-terminates the string, even if it has to // truncate it. template -void strcpy_z(T* dest, const T* src, size_t count) { +void text_strnzcpy_t(T* dest, const T* src, size_t count) { size_t x; for (x = 0; x < count - 1 && src[x] != 0; x++) { dest[x] = src[x]; @@ -39,44 +87,294 @@ void strcpy_z(T* dest, const T* src, size_t count) { +// (2) Type conversion functions + template -void strncpy_t(DestT*, const SrcT*, size_t) { +void text_strnzcpy_t(DestT*, size_t, const SrcT*, size_t) { static_assert(always_false::v, - "unspecialized strcpy_t should never be called"); + "unspecialized text_strnzcpy_t should never be called"); } template <> -inline void strncpy_t(char* dest, const char* src, size_t count) { - strcpy_z(dest, src, count); +inline void text_strnzcpy_t( + char* dest, size_t dest_count, const char* src, size_t src_count) { + size_t count = std::min(dest_count, src_count); + text_strnzcpy_t(dest, src, count); } template <> -inline void strncpy_t(char* dest, const char16_t* src, size_t count) { - encode_sjis(dest, src, count); +inline void text_strnzcpy_t( + char* dest, size_t dest_count, const char16_t* src, size_t src_count) { + encode_sjis(dest, dest_count, src, src_count); } template <> -inline void strncpy_t(char16_t* dest, const char* src, size_t count) { - decode_sjis(dest, src, count); +inline void text_strnzcpy_t( + char16_t* dest, size_t dest_count, const char* src, size_t src_count) { + decode_sjis(dest, dest_count, src, src_count); } template <> -inline void strncpy_t(char16_t* dest, const char16_t* src, size_t count) { - strcpy_z(dest, src, count); +inline void text_strnzcpy_t( + char16_t* dest, size_t dest_count, const char16_t* src, size_t src_count) { + size_t count = std::min(dest_count, src_count); + text_strnzcpy_t(dest, src, count); } -void add_language_marker_inplace(char* s, char marker, size_t dest_count); -void add_language_marker_inplace(char16_t* s, char16_t marker, size_t dest_count); -void remove_language_marker_inplace(char* s); -void remove_language_marker_inplace(char16_t* s); -std::string add_language_marker(const std::string& s, char marker); -std::u16string add_language_marker(const std::u16string& s, char16_t marker); -std::string remove_language_marker(const std::string& s); -std::u16string remove_language_marker(const std::u16string& s); +// (3) Packed text object for use in protocol structs + +template +struct parray { + ItemT items[Count]; + + parray() { + this->clear(); + } + parray(const parray& other) { + this->operator=(other); + } + parray(parray&& s) = delete; + + template + parray(const parray& s) { + this->operator=(s); + } + + constexpr size_t size() { + return Count; + } + constexpr size_t bytes() { + return Count * sizeof(ItemT); + } + ItemT* data() { + return this->items; + } + const ItemT* data() const { + return this->items; + } + + // TODO: These can be made faster by only clearing the unused space after the + // strncpy_t (if any) instead of clearing all the space every time + parray& operator=(const parray& s) { + for (size_t x = 0; x < Count; x++) { + this->items[x] = s.items[x]; + } + return *this; + } + parray& operator=(parray&& s) = delete; + + template + parray& operator=(const parray& s) { + if (OtherCount <= Count) { + size_t x; + for (x = 0; x < OtherCount; x++) { + this->items[x] = s.items[x]; + } + for (; x < Count; x++) { + this->items[x] = 0; + } + } else { + for (size_t x = 0; x < Count; x++) { + this->items[x] = s.items[x]; + } + } + return *this; + } + + parray& operator=(const ItemT* s) { + for (size_t x = 0; x < Count; x++) { + this->items[x] = s[x]; + } + return *this; + } + + bool operator==(const parray& s) const { + for (size_t x = 0; x < Count; x++) { + if (this->items[x] != s.items[x]) { + return false; + } + } + return true; + } + + void clear(ItemT v = 0) { + for (size_t x = 0; x < Count; x++) { + this->items[x] = v; + } + } +} __attribute__((packed)); +template +struct ptext : parray { + ptext() { + this->clear(); + } + ptext(const ptext& other) { + this->operator=(other); + } + ptext(ptext&& s) = delete; + + template + ptext(const OtherCharT* s) { + this->operator=(s); + } + template + ptext(const OtherCharT* s, size_t count) { + this->assign(s, count); + } + template + ptext(const std::basic_string& s) { + this->operator=(s); + } + template + ptext(const ptext& s) { + this->operator=(s); + } + + size_t len() const { + return text_strlen_t(this->items); + } + const CharT* c_str() const { + return this->data(); + } + + // TODO: These can be made faster by only clearing the unused space after the + // strncpy_t (if any) instead of clearing all the space every time + ptext& operator=(const ptext& s) { + this->clear(); + text_strnzcpy_t(this->items, Count, s.items, Count); + return *this; + } + ptext& operator=(ptext&& s) = delete; + + template + ptext& operator=(const OtherCharT* s) { + this->clear(); + text_strnzcpy_t(this->items, Count, s, Count); + return *this; + } + template + ptext& assign(const OtherCharT* s, size_t s_count) { + this->clear(); + text_strnzcpy_t(this->items, Count, s, s_count); + return *this; + } + template + ptext& operator=(const std::basic_string& s) { + this->clear(); + text_strnzcpy_t(this->items, Count, s.c_str(), s.size() + 1); + return *this; + } + template + ptext& operator=(const ptext& s) { + this->clear(); + text_strnzcpy_t(this->items, Count, s.items, OtherCount); + return *this; + } + + template + bool operator==(const OtherCharT* s) const { + return text_streq_t(this->items, s); + } + template + bool operator==(const std::basic_string& s) const { + return text_streq_t(this->items, s.c_str()); + } + template + bool operator==(const ptext& s) const { + return text_streq_t(this->items, s.items); + } + + template + bool eq_n(const OtherCharT* s, size_t count) const { + return text_strneq_t(this->items, s, count); + } + template + bool eq_n(const std::basic_string& s, size_t count) const { + return text_strneq_t(this->items, s.c_str(), count); + } + template + bool eq_n(const ptext& s, size_t count) const { + return text_strneq_t(this->items, s.items, count); + } + + operator std::basic_string() const { + std::basic_string ret(this->items, Count); + strip_trailing_zeroes(ret); + return ret; + } + + bool empty() const { + return (this->items[0] == 0); + } +} __attribute__((packed)); + + + +// (4) Markers and character replacement + +template +std::basic_string add_language_marker( + const std::basic_string& s, CharT marker) { + if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) { + return s; + } + + std::basic_string ret; + ret.push_back('\t'); + ret.push_back(marker); + ret += s; + return ret; +} + +template +std::basic_string add_language_marker( + const ptext& s, CharT marker) { + if ((s.items[0] == '\t') && (s.items[1] != 'C')) { + return s; + } + + std::basic_string ret; + ret.push_back('\t'); + ret.push_back(marker); + ret += s; + return ret; +} + +template +const CharT* remove_language_marker(const CharT* s) { + if ((s[0] != '\t') || (s[1] == 'C')) { + return s; + } + return s + 2; +} + +template +std::basic_string remove_language_marker(const ptext& s) { + if ((s.items[0] != '\t') || (s.items[1] == L'C')) { + return s; + } + return &s.items[2]; +} + +template +std::basic_string remove_language_marker( + const std::basic_string& s) { + if ((s.size() < 2) || (s[0] != L'\t') || (s[1] == L'C')) { + return s; + } + return s.substr(2); +} + +template +void remove_language_marker_inplace(ptext& a) { + if ((a.items[0] == '\t') && (a.items[1] != 'C')) { + text_strnzcpy_t(a.items, Count, &a.items[2], Count); + } +} template void replace_char_inplace(T* a, T f, T r) { @@ -150,3 +448,35 @@ void add_color(StringWriter& w, const T* src, size_t max_input_chars) { } w.put(0); } + +template +void add_color_inplace(ptext& t) { + size_t sx = 0; + size_t dx = 0; + for (; (sx < Count - 1) && t.items[sx]; sx++) { + if (t.items[sx] == '$') { + t.items[dx] = '\t'; + } else if (t.items[sx] == '#') { + t.items[dx] = '\n'; + } else if (t.items[sx] == '%') { + sx++; + if ((sx == Count - 1) || (t.items[sx] == '\0')) { + break; + } else if (t.items[sx] == 's') { + t.items[dx] = '$'; + } else if (t.items[sx] == '%') { + t.items[dx] = '%'; + } else if (t.items[sx] == 'n') { + t.items[dx] = '#'; + } else { + t.items[dx] = t.items[sx]; + } + } else { + t.items[dx] = t.items[sx]; + } + dx++; + } + for (; dx < Count; dx++) { + t.items[dx] = 0; + } +}