diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 749950e8..a4f1c640 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1126,9 +1126,7 @@ static void server_command_edit(shared_ptr c, const std::string& args) { } } else if (tokens.at(0) == "name") { vector orig_tokens = split(args, ' '); - string name = ((p->inventory.language == 0) ? "\tE" : "\tJ") + orig_tokens.at(1); - p->disp.name.clear(); - p->disp.name.encode(name, p->inventory.language); + p->disp.name.encode(orig_tokens.at(1), p->inventory.language); } else if (tokens.at(0) == "npc") { if (tokens.at(1) == "none") { p->disp.visual.extra_model = 0; diff --git a/src/Client.cc b/src/Client.cc index 3d198b4a..a9d11bab 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -815,7 +815,7 @@ void Client::load_all_files() { files_manager->set_character(this->character_filename(), this->character_data); this->character_data->inventory = nsc_data.inventory; this->character_data->disp = nsc_data.disp; - this->character_data->play_time_seconds = nsc_data.disp.play_time; + this->character_data->play_time_seconds = 0; this->character_data->unknown_a2 = nsc_data.unknown_a2; this->character_data->quest_flags = nsc_data.quest_flags; this->character_data->death_count = nsc_data.death_count; @@ -929,8 +929,7 @@ void Client::save_character_file() { // off each time we save. I'm lazy, so insert shrug emoji here. uint64_t t = now(); uint64_t seconds = (t - this->last_play_time_update) / 1000000; - this->character_data->disp.play_time += seconds; - this->character_data->play_time_seconds = this->character_data->disp.play_time; + this->character_data->play_time_seconds += seconds; player_data_log.info("Added %" PRIu64 " seconds to play time", seconds); this->last_play_time_update = t; } diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index dd4a0080..67d1be2a 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1439,26 +1439,28 @@ struct S_GenerateID_DC_PC_V3_80 { // contains uninitialized memory when the client sends this command. newserv // clears the uninitialized data for security reasons before forwarding. -template -struct SC_SimpleMail_81 { +struct SC_SimpleMail_PC_81 { // If player_tag and from_guild_card_number are zero, the message cannot be // replied to. le_uint32_t player_tag = 0x00010000; le_uint32_t from_guild_card_number = 0; - pstring from_name; + pstring from_name; le_uint32_t to_guild_card_number = 0; - pstring text; + pstring text; } __packed__; -struct SC_SimpleMail_PC_81 : SC_SimpleMail_81 { -} __packed__; -struct SC_SimpleMail_DC_V3_81 : SC_SimpleMail_81 { +struct SC_SimpleMail_DC_V3_81 { + le_uint32_t player_tag = 0x00010000; + le_uint32_t from_guild_card_number = 0; + pstring from_name; + le_uint32_t to_guild_card_number = 0; + pstring text; } __packed__; struct SC_SimpleMail_BB_81 { le_uint32_t player_tag = 0x00010000; le_uint32_t from_guild_card_number = 0; - pstring from_name; + pstring from_name; le_uint32_t to_guild_card_number = 0; pstring received_date; pstring text; @@ -2506,7 +2508,7 @@ struct S_ChoiceSearchResultEntry_DC_V3_C4 : S_ChoiceSearchResultEntry_C4 { } __packed__; -struct S_ChoiceSearchResultEntry_BB_C4 : S_ChoiceSearchResultEntry_C4 { +struct S_ChoiceSearchResultEntry_BB_C4 : S_ChoiceSearchResultEntry_C4 { } __packed__; // C5 (S->C): Player records update (DCv2 and later versions) @@ -2703,7 +2705,7 @@ struct S_InfoBoardEntry_D8 { pstring name; pstring message; } __packed__; -struct S_InfoBoardEntry_BB_D8 : S_InfoBoardEntry_D8 { +struct S_InfoBoardEntry_BB_D8 : S_InfoBoardEntry_D8 { } __packed__; struct S_InfoBoardEntry_V3_D8 : S_InfoBoardEntry_D8 { } __packed__; @@ -3336,7 +3338,7 @@ struct C_AddOrRemoveTeamMember_BB_03EA_05EA { // 07EA: Team chat struct SC_TeamChat_BB_07EA { - pstring sender_name; + pstring sender_name; // Text follows here. The message is truncated by the client if it is longer // than 0x8F wchar_ts. } __packed__; @@ -3353,7 +3355,7 @@ struct S_TeamMemberList_BB_09EA { le_uint32_t rank = 0; le_uint32_t privilege_level = 0; // 0x10 or 0x20 = green, 0x30 = blue, 0x40 = red, anything else = white le_uint32_t guild_card_number = 0; - pstring name; + pstring name; } __packed__; // Variable-length field: // Entry entries[entry_count]; @@ -3374,7 +3376,7 @@ struct S_Unknown_BB_0CEA { struct S_TeamName_BB_0EEA { parray unused; - pstring team_name; + pstring team_name; } __packed__; // 0FEA (C->S): Set team flag @@ -3412,7 +3414,7 @@ struct S_TeamMembershipInformation_BB_12EA { uint8_t unknown_a7 = 0; uint8_t unknown_a8 = 0; uint8_t unknown_a9 = 0; - pstring team_name; + pstring team_name; } __packed__; // 13EA: Team info for lobby players @@ -3429,10 +3431,10 @@ struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry { /* 0011 */ uint8_t unknown_a7 = 0; /* 0012 */ uint8_t unknown_a8 = 0; /* 0013 */ uint8_t unknown_a9 = 0; - /* 0014 */ pstring team_name; + /* 0014 */ pstring team_name; /* 0034 */ le_uint32_t guild_card_number2 = 0; /* 0038 */ le_uint32_t lobby_client_id = 0; - /* 003C */ pstring player_name; + /* 003C */ pstring player_name; /* 005C */ parray flag_data; /* 085C */ } __packed__; @@ -3460,7 +3462,7 @@ struct S_IntraTeamRanking_BB_18EA { /* 00 */ le_uint32_t rank = 0; /* 04 */ le_uint32_t privilege_level = 0; /* 08 */ le_uint32_t guild_card_number = 0; - /* 0C */ pstring player_name; + /* 0C */ pstring player_name; /* 2C */ le_uint32_t points = 0; /* 30 */ } __packed__; @@ -3497,7 +3499,7 @@ struct S_TeamRewardList_BB_19EA_1AEA { struct S_CrossTeamRanking_BB_1CEA { le_uint32_t num_entries; struct Entry { - /* 00 */ pstring team_name; + /* 00 */ pstring team_name; /* 20 */ le_uint32_t team_points = 0; /* 24 */ le_uint32_t unknown_a1 = 0; /* 28 */ @@ -3513,7 +3515,7 @@ struct S_CrossTeamRanking_BB_1CEA { // header.flag is used, but it's unknown what the value means. struct C_RenameTeam_BB_1EEA { - pstring new_team_name; + pstring new_team_name; } __packed__; // 1FEA (S->C): Rename team result @@ -4907,7 +4909,7 @@ struct G_SyncPlayerDispAndInventory_BB_6x70 { // Offsets in this struct are relative to the overall command header /* 0008 */ G_ExtendedHeader header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; /* 0010 */ G_SyncPlayerDispAndInventory_BaseV1 base; - /* 0128 */ pstring name; + /* 0128 */ pstring name; /* 0148 */ PlayerStats stats; /* 016C */ le_uint32_t num_items = 0; /* 0170 */ parray items; diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 59e01c05..2af9bee2 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -337,7 +337,7 @@ JSON HTTPServer::generate_game_client_json(shared_ptr c) const { ret.emplace("ProportionY", p->disp.visual.proportion_y.load()); ret.emplace("Name", p->disp.name.decode(c->language())); - ret.emplace("PlayTimeSeconds", p->disp.play_time.load()); + ret.emplace("PlayTimeSeconds", p->play_time_seconds.load()); ret.emplace("AutoReply", p->auto_reply.decode(c->language())); ret.emplace("InfoBoard", p->info_board.decode(c->language())); diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 688f1994..f4b6606c 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -165,10 +165,6 @@ void PlayerVisualConfig::enforce_lobby_join_limits_for_version(Version v) { this->name_color_checksum = 0; } this->class_flags = class_flags_for_class(this->char_class); - - if (!is_v4(v) && (this->name.at(0) == '\t') && (this->name.at(1) == 'J' || this->name.at(1) == 'E')) { - this->name.encode(this->name.decode().substr(2)); - } } void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_version(Version v) { @@ -177,13 +173,6 @@ void PlayerDispDataDCPCV3::enforce_lobby_join_limits_for_version(Version v) { void PlayerDispDataBB::enforce_lobby_join_limits_for_version(Version v) { this->visual.enforce_lobby_join_limits_for_version(v); - if (!is_v4(v)) { - throw logic_error("PlayerDispDataBB being sent to non-BB client"); - } - this->play_time = 0; - if (this->name.at(0) != '\t' || (this->name.at(1) != 'E' && this->name.at(1) != 'J')) { - this->name.encode("\tJ" + this->name.decode()); - } } PlayerDispDataBB PlayerDispDataDCPCV3::to_bb(uint8_t to_language, uint8_t from_language) const { @@ -209,16 +198,6 @@ PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3(uint8_t to_language, uint8_t fr return ret; } -PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const { - PlayerDispDataBBPreview pre; - pre.level = this->stats.level; - pre.experience = this->stats.experience; - pre.visual = this->visual; - pre.name = this->name; - pre.play_time = this->play_time; - return pre; -} - void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { this->visual.name_color = pre.visual.name_color; this->visual.extra_model = pre.visual.extra_model; @@ -343,7 +322,7 @@ PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsPC_Chall grave_message(rec.grave_message.decode(), 1), unknown_m5(0), unknown_t6(0), - rank_title(rec.rank_title), + rank_title(rec.rank_title.decode(), 1), unknown_l7(0) {} PlayerRecordsBB_Challenge::PlayerRecordsBB_Challenge(const PlayerRecordsV3_Challenge& rec) @@ -407,7 +386,7 @@ PlayerRecordsBB_Challenge::operator PlayerRecordsPC_Challenge() const { PlayerRecordsPC_Challenge ret; ret.title_color = this->title_color; ret.unknown_u0 = this->unknown_u0; - ret.rank_title = this->rank_title; + ret.rank_title.encode(this->rank_title.decode()); ret.times_ep1_online = this->times_ep1_online; if (this->grave_is_ep2) { ret.grave_stage_num = 0; diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 77f20655..c607dce6 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -174,8 +174,8 @@ struct PlayerDispDataBBPreview { // The name field in this structure is used for the player's Guild Card // number, apparently (possibly because it's a char array and this is BB) /* 08 */ PlayerVisualConfig visual; - /* 58 */ pstring name; - /* 78 */ uint32_t play_time = 0; + /* 58 */ pstring name; + /* 78 */ uint32_t play_time_seconds = 0; /* 7C */ } __attribute__((packed)); @@ -183,16 +183,14 @@ struct PlayerDispDataBBPreview { struct PlayerDispDataBB { /* 0000 */ PlayerStats stats; /* 0024 */ PlayerVisualConfig visual; - /* 0074 */ pstring name; - /* 008C */ le_uint32_t play_time = 0; - /* 0090 */ uint32_t unknown_a3 = 0; + /* 0074 */ pstring name; + /* 008C */ parray unknown_a1; // Probably actually unused /* 0094 */ parray config; /* 017C */ parray technique_levels_v1; /* 0190 */ void enforce_lobby_join_limits_for_version(Version v); PlayerDispDataDCPCV3 to_dcpcv3(uint8_t to_language, uint8_t from_language) const; - PlayerDispDataBBPreview to_preview() const; void apply_preview(const PlayerDispDataBBPreview&); void apply_dressing_room(const PlayerDispDataBBPreview&); } __attribute__((packed)); @@ -251,8 +249,8 @@ struct GuildCardXB { struct GuildCardBB { /* 0000 */ le_uint32_t guild_card_number = 0; - /* 0004 */ pstring name; - /* 0034 */ pstring team_name; + /* 0004 */ pstring name; + /* 0034 */ pstring team_name; /* 0054 */ pstring description; /* 0104 */ uint8_t present = 0; /* 0105 */ uint8_t language = 0; @@ -320,7 +318,7 @@ struct PlayerLobbyDataBB { /* 0C */ le_uint32_t team_id = 0; /* 10 */ parray unknown_a1; /* 1C */ le_uint32_t client_id = 0; - /* 20 */ pstring name; + /* 20 */ pstring name; // If this field is zero, the "Press F1 for help" prompt appears in the corner // of the screen in the lobby and on Pioneer 2. /* 40 */ le_uint32_t hide_help_prompt = 1; @@ -441,7 +439,7 @@ struct PlayerRecordsBB_Challenge { /* 00F4 */ ChallengeAwardState ep1_online_award_state; /* 00FC */ ChallengeAwardState ep2_online_award_state; /* 0104 */ ChallengeAwardState ep1_offline_award_state; - /* 010C */ pstring rank_title; // Encrypted; see decrypt_challenge_rank_text + /* 010C */ pstring rank_title; /* 0124 */ parray unknown_l7; /* 0140 */ diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index dfa5fce3..96e4e398 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -518,7 +518,7 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 ses->log.info("Remote guild card number set to %" PRId64, ses->remote_guild_card_number); string message = string_printf( - "The remote server\nhas assigned your\nGuild Card number:\n\tC6%" PRId64, + "The remote server\nhas assigned your\nGuild Card number:\n$C6%" PRId64, ses->remote_guild_card_number); send_ship_info(ses->client_channel, message); } diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 4ac467a0..a3a9b83c 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -826,7 +826,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) this->disconnect(); } else { - send_ship_info(this->client_channel, string_printf("You\'ve returned to\n\tC6%s$C7\n\n%s", s->name.c_str(), error_message ? error_message : "")); + send_ship_info(this->client_channel, string_printf("You\'ve returned to\n$C6%s$C7\n\n%s", s->name.c_str(), error_message ? error_message : "")); // Restore newserv_client_config, so the login server gets the client flags if (is_v3(this->version())) { diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 0bcccdd2..ade0dd7f 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -51,6 +51,7 @@ static string escape_string(const string& data, TextEncoding encoding = TextEnco decoded = data; break; case TextEncoding::UTF16: + case TextEncoding::UTF16_ALWAYS_MARKED: decoded = tt_utf16_to_utf8(data); break; case TextEncoding::SJIS: diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 529d6d22..dc4bd59b 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3168,9 +3168,6 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri c->license->save(); string name_str = player->disp.name.decode(c->language()); - if ((name_str.size() > 2) && (name_str[0] == '\t') && ((name_str[1] == 'E') || (name_str[1] == 'J'))) { - name_str = name_str.substr(2); - } c->channel.name = string_printf("C-%" PRIX64 " (%s)", c->id, name_str.c_str()); // 98 should only be sent when leaving a game, and we should leave the client @@ -3303,9 +3300,6 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { auto p = c->character(); string from_name = p->disp.name.decode(c->language()); - if (from_name.size() >= 2 && from_name[0] == '\t' && (from_name[1] == 'E' || from_name[1] == 'J')) { - from_name = from_name.substr(2); - } static const string whisper_text = "(whisper)"; for (size_t x = 0; x < l->max_clients; x++) { if (l->clients[x]) { @@ -3368,7 +3362,7 @@ static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { auto s = c->require_server_state(); try { - auto preview = c->character()->disp.to_preview(); + auto preview = c->character()->to_preview(); send_player_preview_bb(c, cmd.character_index, &preview); } catch (const exception& e) { diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 2b43a996..201a00c8 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -587,9 +587,6 @@ public: this->xb_user_id = this->default_xb_user_id(); this->xb_unknown_a16 = cmd.unknown_a16; this->name = cmd.name.decode(cmd.base.language); - if ((this->name.size() > 2) && (this->name[0] == '\t') && ((this->name[1] == 'E') || (this->name[1] == 'J'))) { - this->name = this->name.substr(2); - } this->visual.name.encode(this->name, cmd.base.language); } @@ -663,7 +660,7 @@ public: G_SyncPlayerDispAndInventory_BB_6x70 as_bb(uint8_t language) const { G_SyncPlayerDispAndInventory_BB_6x70 ret; ret.base = this->base_v1(); - ret.name.encode("\tJ" + this->name, language); + ret.name.encode(this->name, language); ret.base.visual.name.encode(string_printf("%10" PRId32, this->guild_card_number), language); ret.stats = this->stats; ret.num_items = this->num_items; diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 3a735ab5..f556e2a6 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -210,6 +210,16 @@ PSOBBBaseSystemFile::PSOBBBaseSystemFile() { } } +PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const { + PlayerDispDataBBPreview pre; + pre.level = this->disp.stats.level; + pre.experience = this->disp.stats.experience; + pre.visual = this->disp.visual; + pre.name = this->disp.name; + pre.play_time_seconds = this->play_time_seconds; + return pre; +} + shared_ptr PSOBBCharacterFile::create_from_config( uint32_t guild_card_number, uint8_t language, diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 1af5b390..3a2913ca 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -142,7 +142,7 @@ struct PSOBBTeamMembership { /* 0011 */ uint8_t unknown_a7 = 0; /* 0012 */ uint8_t unknown_a8 = 0; /* 0013 */ uint8_t unknown_a9 = 0; - /* 0014 */ pstring team_name; + /* 0014 */ pstring team_name; /* 0034 */ parray flag_data; /* 0834 */ le_uint32_t reward_flags = 0; /* 0838 */ @@ -173,7 +173,7 @@ struct PSOBBFullSystemFile { struct PSOBBCharacterFile { struct SymbolChatEntry { /* 00 */ le_uint32_t present = 0; - /* 04 */ pstring name; + /* 04 */ pstring name; /* 2C */ SymbolChat data; /* 68 */ } __attribute__((packed)); @@ -219,6 +219,8 @@ struct PSOBBCharacterFile { PSOBBCharacterFile() = default; + PlayerDispDataBBPreview to_preview() const; + static std::shared_ptr create_from_config( uint32_t guild_card_number, uint8_t language, @@ -784,6 +786,6 @@ struct LegacySavedAccountDataBB { // .nsa file format /* E13C */ le_uint32_t option_flags; /* E140 */ parray shortcuts; /* EB80 */ parray symbol_chats; - /* F060 */ pstring team_name; + /* F060 */ pstring team_name; /* F080 */ } __attribute__((packed)); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 317058e6..a98d4318 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -653,7 +653,6 @@ void send_complete_player_bb(shared_ptr c) { if (team) { cmd.system_file.team_membership = team->membership_for_member(c->license->serial_number); } - cmd.char_file.disp.play_time = 0; send_command_t(c, 0x00E7, 0x00000000, cmd); } @@ -837,7 +836,7 @@ string prepare_chat_data( string data; if (version == Version::BB_V4) { - data.append("\tJ"); + data.append(language ? "\tE" : "\tJ"); } data.append(from_name); if (version == Version::DC_NTE) { @@ -1415,7 +1414,9 @@ void send_game_menu( shared_ptr c, bool is_spectator_team_list, bool show_tournaments_only) { - if (uses_utf16(c->version())) { + if (is_v4(c->version())) { + send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); + } else if (uses_utf16(c->version())) { send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); } else { send_game_menu_t(c, is_spectator_team_list, show_tournaments_only); @@ -1639,12 +1640,7 @@ void populate_lobby_data_for_client(PlayerLobbyDataBB& ret, s ret.team_id = 0; } string name = c->character()->disp.name.decode(c->language()); - if ((name.size() >= 2) && (name[0] == '\t') && (name[1] != 'C')) { - ret.name.encode(name, viewer_c->language()); - } else { - const char* marker = c->language() ? "\tE" : "\tJ"; - ret.name.encode(marker + name, viewer_c->language()); - } + ret.name.encode(name, viewer_c->language()); } static void send_join_spectator_team(shared_ptr c, shared_ptr l) { diff --git a/src/TeamIndex.cc b/src/TeamIndex.cc index 159791ef..2a05a79b 100644 --- a/src/TeamIndex.cc +++ b/src/TeamIndex.cc @@ -136,7 +136,7 @@ PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t serial_numbe ret.unknown_a7 = 0; ret.unknown_a8 = 0; ret.unknown_a9 = 0; - ret.team_name.encode("\tE" + this->name); + ret.team_name.encode(this->name); if (this->flag_data) { ret.flag_data = *this->flag_data; } else { diff --git a/src/Text.hh b/src/Text.hh index 34d69420..d1039afe 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -252,6 +252,7 @@ enum class TextEncoding { ISO8859, ASCII, MARKED, + UTF16_ALWAYS_MARKED, CHALLENGE8, // MARKED but with challenge encryption on top CHALLENGE16, // UTF16 but with challenge encryption on top }; @@ -290,7 +291,7 @@ void decrypt_challenge_rank_text_t(void* vdata, size_t count) { template < TextEncoding Encoding, size_t Chars, - size_t BytesPerChar = (((Encoding == TextEncoding::UTF16) || (Encoding == TextEncoding::CHALLENGE16)) ? 2 : 1)> + size_t BytesPerChar = (((Encoding == TextEncoding::UTF16) || (Encoding == TextEncoding::UTF16_ALWAYS_MARKED) || (Encoding == TextEncoding::CHALLENGE16)) ? 2 : 1)> struct pstring { static constexpr size_t Bytes = Chars * BytesPerChar; @@ -349,6 +350,17 @@ struct pstring { this->clear_after_bytes(ret.bytes_written); break; } + case TextEncoding::UTF16_ALWAYS_MARKED: + if (s.empty()) { + this->clear(); + break; + } else if (s.size() <= 2 || s[0] != '\t' || s[1] == 'C') { + std::string to_encode = ((client_language == 0) ? "\tJ" : "\tE") + s; + auto ret = tt_utf8_to_utf16(this->data, Bytes, to_encode.data(), to_encode.size(), true); + this->clear_after_bytes(ret.bytes_written); + break; + } + [[fallthrough]]; case TextEncoding::UTF16: { auto ret = tt_utf8_to_utf16(this->data, Bytes, s.data(), s.size(), true); this->clear_after_bytes(ret.bytes_written); @@ -437,6 +449,10 @@ struct pstring { return tt_sjis_to_utf8(this->data, this->used_chars_8()); case TextEncoding::UTF16: return tt_utf16_to_utf8(this->data, this->used_chars_16() * 2); + case TextEncoding::UTF16_ALWAYS_MARKED: { + std::string ret = tt_utf16_to_utf8(this->data, this->used_chars_16() * 2); + return ((ret.size() >= 2) && (ret[0] == '\t') && (ret[1] != 'C')) ? ret.substr(2) : ret; + } case TextEncoding::UTF8: return std::string(reinterpret_cast(&this->data[0]), this->used_chars_8()); case TextEncoding::CHALLENGE16: {